OSDN Git Service

Merge "DO NOT MERGE. KEY_INTENT shouldn't grant permissions." into nyc-dev am: 254369...
[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.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
22 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
23
24 import android.R;
25 import android.annotation.ColorInt;
26 import android.annotation.DrawableRes;
27 import android.annotation.FloatRange;
28 import android.annotation.IntDef;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.annotation.Size;
32 import android.annotation.StringRes;
33 import android.annotation.StyleRes;
34 import android.annotation.XmlRes;
35 import android.app.Activity;
36 import android.app.assist.AssistStructure;
37 import android.content.ClipData;
38 import android.content.ClipDescription;
39 import android.content.ClipboardManager;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.UndoManager;
43 import android.content.res.ColorStateList;
44 import android.content.res.CompatibilityInfo;
45 import android.content.res.Configuration;
46 import android.content.res.Resources;
47 import android.content.res.TypedArray;
48 import android.content.res.XmlResourceParser;
49 import android.graphics.BaseCanvas;
50 import android.graphics.Canvas;
51 import android.graphics.Insets;
52 import android.graphics.Paint;
53 import android.graphics.Path;
54 import android.graphics.PorterDuff;
55 import android.graphics.Rect;
56 import android.graphics.RectF;
57 import android.graphics.Typeface;
58 import android.graphics.drawable.Drawable;
59 import android.graphics.fonts.FontVariationAxis;
60 import android.icu.text.DecimalFormatSymbols;
61 import android.os.AsyncTask;
62 import android.os.Build.VERSION_CODES;
63 import android.os.Bundle;
64 import android.os.LocaleList;
65 import android.os.Parcel;
66 import android.os.Parcelable;
67 import android.os.ParcelableParcel;
68 import android.os.SystemClock;
69 import android.provider.Settings;
70 import android.text.BoringLayout;
71 import android.text.DynamicLayout;
72 import android.text.Editable;
73 import android.text.GetChars;
74 import android.text.GraphicsOperations;
75 import android.text.InputFilter;
76 import android.text.InputType;
77 import android.text.Layout;
78 import android.text.ParcelableSpan;
79 import android.text.Selection;
80 import android.text.SpanWatcher;
81 import android.text.Spannable;
82 import android.text.SpannableStringBuilder;
83 import android.text.Spanned;
84 import android.text.SpannedString;
85 import android.text.StaticLayout;
86 import android.text.TextDirectionHeuristic;
87 import android.text.TextDirectionHeuristics;
88 import android.text.TextPaint;
89 import android.text.TextUtils;
90 import android.text.TextUtils.TruncateAt;
91 import android.text.TextWatcher;
92 import android.text.method.AllCapsTransformationMethod;
93 import android.text.method.ArrowKeyMovementMethod;
94 import android.text.method.DateKeyListener;
95 import android.text.method.DateTimeKeyListener;
96 import android.text.method.DialerKeyListener;
97 import android.text.method.DigitsKeyListener;
98 import android.text.method.KeyListener;
99 import android.text.method.LinkMovementMethod;
100 import android.text.method.MetaKeyKeyListener;
101 import android.text.method.MovementMethod;
102 import android.text.method.PasswordTransformationMethod;
103 import android.text.method.SingleLineTransformationMethod;
104 import android.text.method.TextKeyListener;
105 import android.text.method.TimeKeyListener;
106 import android.text.method.TransformationMethod;
107 import android.text.method.TransformationMethod2;
108 import android.text.method.WordIterator;
109 import android.text.style.CharacterStyle;
110 import android.text.style.ClickableSpan;
111 import android.text.style.ParagraphStyle;
112 import android.text.style.SpellCheckSpan;
113 import android.text.style.SuggestionSpan;
114 import android.text.style.URLSpan;
115 import android.text.style.UpdateAppearance;
116 import android.text.util.Linkify;
117 import android.util.AttributeSet;
118 import android.util.DisplayMetrics;
119 import android.util.IntArray;
120 import android.util.Log;
121 import android.util.TypedValue;
122 import android.view.AccessibilityIterators.TextSegmentIterator;
123 import android.view.ActionMode;
124 import android.view.Choreographer;
125 import android.view.ContextMenu;
126 import android.view.DragEvent;
127 import android.view.Gravity;
128 import android.view.HapticFeedbackConstants;
129 import android.view.InputDevice;
130 import android.view.KeyCharacterMap;
131 import android.view.KeyEvent;
132 import android.view.MotionEvent;
133 import android.view.PointerIcon;
134 import android.view.View;
135 import android.view.ViewConfiguration;
136 import android.view.ViewDebug;
137 import android.view.ViewGroup.LayoutParams;
138 import android.view.ViewHierarchyEncoder;
139 import android.view.ViewParent;
140 import android.view.ViewRootImpl;
141 import android.view.ViewStructure;
142 import android.view.ViewTreeObserver;
143 import android.view.accessibility.AccessibilityEvent;
144 import android.view.accessibility.AccessibilityManager;
145 import android.view.accessibility.AccessibilityNodeInfo;
146 import android.view.animation.AnimationUtils;
147 import android.view.autofill.AutofillManager;
148 import android.view.autofill.AutofillValue;
149 import android.view.inputmethod.BaseInputConnection;
150 import android.view.inputmethod.CompletionInfo;
151 import android.view.inputmethod.CorrectionInfo;
152 import android.view.inputmethod.CursorAnchorInfo;
153 import android.view.inputmethod.EditorInfo;
154 import android.view.inputmethod.ExtractedText;
155 import android.view.inputmethod.ExtractedTextRequest;
156 import android.view.inputmethod.InputConnection;
157 import android.view.inputmethod.InputMethodManager;
158 import android.view.textclassifier.TextClassificationManager;
159 import android.view.textclassifier.TextClassifier;
160 import android.view.textservice.SpellCheckerSubtype;
161 import android.view.textservice.TextServicesManager;
162 import android.widget.RemoteViews.RemoteView;
163
164 import com.android.internal.annotations.VisibleForTesting;
165 import com.android.internal.logging.MetricsLogger;
166 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
167 import com.android.internal.util.FastMath;
168 import com.android.internal.widget.EditableInputConnection;
169
170 import libcore.util.EmptyArray;
171
172 import org.xmlpull.v1.XmlPullParserException;
173
174 import java.io.IOException;
175 import java.lang.annotation.Retention;
176 import java.lang.annotation.RetentionPolicy;
177 import java.lang.ref.WeakReference;
178 import java.util.ArrayList;
179 import java.util.Arrays;
180 import java.util.Locale;
181
182 /**
183  * A user interface element that displays text to the user.
184  * To provide user-editable text, see {@link EditText}.
185  * <p>
186  * The following code sample shows a typical use, with an XML layout
187  * and code to modify the contents of the text view:
188  * </p>
189
190  * <pre>
191  * &lt;LinearLayout
192        xmlns:android="http://schemas.android.com/apk/res/android"
193        android:layout_width="match_parent"
194        android:layout_height="match_parent"&gt;
195  *    &lt;TextView
196  *        android:id="@+id/text_view_id"
197  *        android:layout_height="wrap_content"
198  *        android:layout_width="wrap_content"
199  *        android:text="@string/hello" /&gt;
200  * &lt;/LinearLayout&gt;
201  * </pre>
202  * <p>
203  * This code sample demonstrates how to modify the contents of the text view
204  * defined in the previous XML layout:
205  * </p>
206  * <pre>
207  * public class MainActivity extends Activity {
208  *
209  *    protected void onCreate(Bundle savedInstanceState) {
210  *         super.onCreate(savedInstanceState);
211  *         setContentView(R.layout.activity_main);
212  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
213  *         helloTextView.setText(R.string.user_greeting);
214  *     }
215  * }
216  * </pre>
217  * <p>
218  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
219  * </p>
220  * <p>
221  * <b>XML attributes</b>
222  * <p>
223  * See {@link android.R.styleable#TextView TextView Attributes},
224  * {@link android.R.styleable#View View Attributes}
225  *
226  * @attr ref android.R.styleable#TextView_text
227  * @attr ref android.R.styleable#TextView_bufferType
228  * @attr ref android.R.styleable#TextView_hint
229  * @attr ref android.R.styleable#TextView_textColor
230  * @attr ref android.R.styleable#TextView_textColorHighlight
231  * @attr ref android.R.styleable#TextView_textColorHint
232  * @attr ref android.R.styleable#TextView_textAppearance
233  * @attr ref android.R.styleable#TextView_textColorLink
234  * @attr ref android.R.styleable#TextView_textSize
235  * @attr ref android.R.styleable#TextView_textScaleX
236  * @attr ref android.R.styleable#TextView_fontFamily
237  * @attr ref android.R.styleable#TextView_typeface
238  * @attr ref android.R.styleable#TextView_textStyle
239  * @attr ref android.R.styleable#TextView_cursorVisible
240  * @attr ref android.R.styleable#TextView_maxLines
241  * @attr ref android.R.styleable#TextView_maxHeight
242  * @attr ref android.R.styleable#TextView_lines
243  * @attr ref android.R.styleable#TextView_height
244  * @attr ref android.R.styleable#TextView_minLines
245  * @attr ref android.R.styleable#TextView_minHeight
246  * @attr ref android.R.styleable#TextView_maxEms
247  * @attr ref android.R.styleable#TextView_maxWidth
248  * @attr ref android.R.styleable#TextView_ems
249  * @attr ref android.R.styleable#TextView_width
250  * @attr ref android.R.styleable#TextView_minEms
251  * @attr ref android.R.styleable#TextView_minWidth
252  * @attr ref android.R.styleable#TextView_gravity
253  * @attr ref android.R.styleable#TextView_scrollHorizontally
254  * @attr ref android.R.styleable#TextView_password
255  * @attr ref android.R.styleable#TextView_singleLine
256  * @attr ref android.R.styleable#TextView_selectAllOnFocus
257  * @attr ref android.R.styleable#TextView_includeFontPadding
258  * @attr ref android.R.styleable#TextView_maxLength
259  * @attr ref android.R.styleable#TextView_shadowColor
260  * @attr ref android.R.styleable#TextView_shadowDx
261  * @attr ref android.R.styleable#TextView_shadowDy
262  * @attr ref android.R.styleable#TextView_shadowRadius
263  * @attr ref android.R.styleable#TextView_autoLink
264  * @attr ref android.R.styleable#TextView_linksClickable
265  * @attr ref android.R.styleable#TextView_numeric
266  * @attr ref android.R.styleable#TextView_digits
267  * @attr ref android.R.styleable#TextView_phoneNumber
268  * @attr ref android.R.styleable#TextView_inputMethod
269  * @attr ref android.R.styleable#TextView_capitalize
270  * @attr ref android.R.styleable#TextView_autoText
271  * @attr ref android.R.styleable#TextView_editable
272  * @attr ref android.R.styleable#TextView_freezesText
273  * @attr ref android.R.styleable#TextView_ellipsize
274  * @attr ref android.R.styleable#TextView_drawableTop
275  * @attr ref android.R.styleable#TextView_drawableBottom
276  * @attr ref android.R.styleable#TextView_drawableRight
277  * @attr ref android.R.styleable#TextView_drawableLeft
278  * @attr ref android.R.styleable#TextView_drawableStart
279  * @attr ref android.R.styleable#TextView_drawableEnd
280  * @attr ref android.R.styleable#TextView_drawablePadding
281  * @attr ref android.R.styleable#TextView_drawableTint
282  * @attr ref android.R.styleable#TextView_drawableTintMode
283  * @attr ref android.R.styleable#TextView_lineSpacingExtra
284  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
285  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
286  * @attr ref android.R.styleable#TextView_inputType
287  * @attr ref android.R.styleable#TextView_imeOptions
288  * @attr ref android.R.styleable#TextView_privateImeOptions
289  * @attr ref android.R.styleable#TextView_imeActionLabel
290  * @attr ref android.R.styleable#TextView_imeActionId
291  * @attr ref android.R.styleable#TextView_editorExtras
292  * @attr ref android.R.styleable#TextView_elegantTextHeight
293  * @attr ref android.R.styleable#TextView_letterSpacing
294  * @attr ref android.R.styleable#TextView_fontFeatureSettings
295  * @attr ref android.R.styleable#TextView_breakStrategy
296  * @attr ref android.R.styleable#TextView_hyphenationFrequency
297  * @attr ref android.R.styleable#TextView_autoSizeTextType
298  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
299  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
300  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
301  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
302  */
303 @RemoteView
304 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
305     static final String LOG_TAG = "TextView";
306     static final boolean DEBUG_EXTRACT = false;
307     static final boolean DEBUG_AUTOFILL = false;
308     private static final float[] TEMP_POSITION = new float[2];
309
310     // Enum for the "typeface" XML parameter.
311     // TODO: How can we get this from the XML instead of hardcoding it here?
312     private static final int SANS = 1;
313     private static final int SERIF = 2;
314     private static final int MONOSPACE = 3;
315
316     // Bitfield for the "numeric" XML parameter.
317     // TODO: How can we get this from the XML instead of hardcoding it here?
318     private static final int SIGNED = 2;
319     private static final int DECIMAL = 4;
320
321     /**
322      * Draw marquee text with fading edges as usual
323      */
324     private static final int MARQUEE_FADE_NORMAL = 0;
325
326     /**
327      * Draw marquee text as ellipsize end while inactive instead of with the fade.
328      * (Useful for devices where the fade can be expensive if overdone)
329      */
330     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
331
332     /**
333      * Draw marquee text with fading edges because it is currently active/animating.
334      */
335     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
336
337     private static final int LINES = 1;
338     private static final int EMS = LINES;
339     private static final int PIXELS = 2;
340
341     private static final RectF TEMP_RECTF = new RectF();
342
343     /** @hide */
344     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
345     private static final int ANIMATED_SCROLL_GAP = 250;
346
347     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
348     private static final Spanned EMPTY_SPANNED = new SpannedString("");
349
350     private static final int CHANGE_WATCHER_PRIORITY = 100;
351
352     // New state used to change background based on whether this TextView is multiline.
353     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
354
355     // Accessibility action to share selected text.
356     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
357
358     /**
359      * @hide
360      */
361     // Accessibility action start id for "process text" actions.
362     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
363
364     /**
365      * @hide
366      */
367     static final int PROCESS_TEXT_REQUEST_CODE = 100;
368
369     /**
370      *  Return code of {@link #doKeyDown}.
371      */
372     private static final int KEY_EVENT_NOT_HANDLED = 0;
373     private static final int KEY_EVENT_HANDLED = -1;
374     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
375     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
376
377     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
378
379     // System wide time for last cut, copy or text changed action.
380     static long sLastCutCopyOrTextChangedTime;
381
382     private ColorStateList mTextColor;
383     private ColorStateList mHintTextColor;
384     private ColorStateList mLinkTextColor;
385     @ViewDebug.ExportedProperty(category = "text")
386     private int mCurTextColor;
387     private int mCurHintTextColor;
388     private boolean mFreezesText;
389
390     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
391     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
392
393     private float mShadowRadius, mShadowDx, mShadowDy;
394     private int mShadowColor;
395
396     private boolean mPreDrawRegistered;
397     private boolean mPreDrawListenerDetached;
398
399     private TextClassifier mTextClassifier;
400
401     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
402     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
403     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
404     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
405     // into by the user holding the movement key down) then we shouldn't prevent the focus from
406     // changing.
407     private boolean mPreventDefaultMovement;
408
409     private TextUtils.TruncateAt mEllipsize;
410
411     static class Drawables {
412         static final int LEFT = 0;
413         static final int TOP = 1;
414         static final int RIGHT = 2;
415         static final int BOTTOM = 3;
416
417         static final int DRAWABLE_NONE = -1;
418         static final int DRAWABLE_RIGHT = 0;
419         static final int DRAWABLE_LEFT = 1;
420
421         final Rect mCompoundRect = new Rect();
422
423         final Drawable[] mShowing = new Drawable[4];
424
425         ColorStateList mTintList;
426         PorterDuff.Mode mTintMode;
427         boolean mHasTint;
428         boolean mHasTintMode;
429
430         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
431         Drawable mDrawableLeftInitial, mDrawableRightInitial;
432
433         boolean mIsRtlCompatibilityMode;
434         boolean mOverride;
435
436         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
437                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
438
439         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
440                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
441
442         int mDrawablePadding;
443
444         int mDrawableSaved = DRAWABLE_NONE;
445
446         public Drawables(Context context) {
447             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
448             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
449                     || !context.getApplicationInfo().hasRtlSupport();
450             mOverride = false;
451         }
452
453         /**
454          * @return {@code true} if this object contains metadata that needs to
455          *         be retained, {@code false} otherwise
456          */
457         public boolean hasMetadata() {
458             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
459         }
460
461         /**
462          * Updates the list of displayed drawables to account for the current
463          * layout direction.
464          *
465          * @param layoutDirection the current layout direction
466          * @return {@code true} if the displayed drawables changed
467          */
468         public boolean resolveWithLayoutDirection(int layoutDirection) {
469             final Drawable previousLeft = mShowing[Drawables.LEFT];
470             final Drawable previousRight = mShowing[Drawables.RIGHT];
471
472             // First reset "left" and "right" drawables to their initial values
473             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
474             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
475
476             if (mIsRtlCompatibilityMode) {
477                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
478                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
479                     mShowing[Drawables.LEFT] = mDrawableStart;
480                     mDrawableSizeLeft = mDrawableSizeStart;
481                     mDrawableHeightLeft = mDrawableHeightStart;
482                 }
483                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
484                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
485                     mShowing[Drawables.RIGHT] = mDrawableEnd;
486                     mDrawableSizeRight = mDrawableSizeEnd;
487                     mDrawableHeightRight = mDrawableHeightEnd;
488                 }
489             } else {
490                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
491                 // drawable if and only if they have been defined
492                 switch(layoutDirection) {
493                     case LAYOUT_DIRECTION_RTL:
494                         if (mOverride) {
495                             mShowing[Drawables.RIGHT] = mDrawableStart;
496                             mDrawableSizeRight = mDrawableSizeStart;
497                             mDrawableHeightRight = mDrawableHeightStart;
498
499                             mShowing[Drawables.LEFT] = mDrawableEnd;
500                             mDrawableSizeLeft = mDrawableSizeEnd;
501                             mDrawableHeightLeft = mDrawableHeightEnd;
502                         }
503                         break;
504
505                     case LAYOUT_DIRECTION_LTR:
506                     default:
507                         if (mOverride) {
508                             mShowing[Drawables.LEFT] = mDrawableStart;
509                             mDrawableSizeLeft = mDrawableSizeStart;
510                             mDrawableHeightLeft = mDrawableHeightStart;
511
512                             mShowing[Drawables.RIGHT] = mDrawableEnd;
513                             mDrawableSizeRight = mDrawableSizeEnd;
514                             mDrawableHeightRight = mDrawableHeightEnd;
515                         }
516                         break;
517                 }
518             }
519
520             applyErrorDrawableIfNeeded(layoutDirection);
521
522             return mShowing[Drawables.LEFT] != previousLeft
523                     || mShowing[Drawables.RIGHT] != previousRight;
524         }
525
526         public void setErrorDrawable(Drawable dr, TextView tv) {
527             if (mDrawableError != dr && mDrawableError != null) {
528                 mDrawableError.setCallback(null);
529             }
530             mDrawableError = dr;
531
532             if (mDrawableError != null) {
533                 final Rect compoundRect = mCompoundRect;
534                 final int[] state = tv.getDrawableState();
535
536                 mDrawableError.setState(state);
537                 mDrawableError.copyBounds(compoundRect);
538                 mDrawableError.setCallback(tv);
539                 mDrawableSizeError = compoundRect.width();
540                 mDrawableHeightError = compoundRect.height();
541             } else {
542                 mDrawableSizeError = mDrawableHeightError = 0;
543             }
544         }
545
546         private void applyErrorDrawableIfNeeded(int layoutDirection) {
547             // first restore the initial state if needed
548             switch (mDrawableSaved) {
549                 case DRAWABLE_LEFT:
550                     mShowing[Drawables.LEFT] = mDrawableTemp;
551                     mDrawableSizeLeft = mDrawableSizeTemp;
552                     mDrawableHeightLeft = mDrawableHeightTemp;
553                     break;
554                 case DRAWABLE_RIGHT:
555                     mShowing[Drawables.RIGHT] = mDrawableTemp;
556                     mDrawableSizeRight = mDrawableSizeTemp;
557                     mDrawableHeightRight = mDrawableHeightTemp;
558                     break;
559                 case DRAWABLE_NONE:
560                 default:
561             }
562             // then, if needed, assign the Error drawable to the correct location
563             if (mDrawableError != null) {
564                 switch(layoutDirection) {
565                     case LAYOUT_DIRECTION_RTL:
566                         mDrawableSaved = DRAWABLE_LEFT;
567
568                         mDrawableTemp = mShowing[Drawables.LEFT];
569                         mDrawableSizeTemp = mDrawableSizeLeft;
570                         mDrawableHeightTemp = mDrawableHeightLeft;
571
572                         mShowing[Drawables.LEFT] = mDrawableError;
573                         mDrawableSizeLeft = mDrawableSizeError;
574                         mDrawableHeightLeft = mDrawableHeightError;
575                         break;
576                     case LAYOUT_DIRECTION_LTR:
577                     default:
578                         mDrawableSaved = DRAWABLE_RIGHT;
579
580                         mDrawableTemp = mShowing[Drawables.RIGHT];
581                         mDrawableSizeTemp = mDrawableSizeRight;
582                         mDrawableHeightTemp = mDrawableHeightRight;
583
584                         mShowing[Drawables.RIGHT] = mDrawableError;
585                         mDrawableSizeRight = mDrawableSizeError;
586                         mDrawableHeightRight = mDrawableHeightError;
587                         break;
588                 }
589             }
590         }
591     }
592
593     Drawables mDrawables;
594
595     private CharWrapper mCharWrapper;
596
597     private Marquee mMarquee;
598     private boolean mRestartMarquee;
599
600     private int mMarqueeRepeatLimit = 3;
601
602     private int mLastLayoutDirection = -1;
603
604     /**
605      * On some devices the fading edges add a performance penalty if used
606      * extensively in the same layout. This mode indicates how the marquee
607      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
608      */
609     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
610
611     /**
612      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
613      * the layout that should be used when the mode switches.
614      */
615     private Layout mSavedMarqueeModeLayout;
616
617     @ViewDebug.ExportedProperty(category = "text")
618     private CharSequence mText;
619     private CharSequence mTransformed;
620     private BufferType mBufferType = BufferType.NORMAL;
621
622     private CharSequence mHint;
623     private Layout mHintLayout;
624
625     private MovementMethod mMovement;
626
627     private TransformationMethod mTransformation;
628     private boolean mAllowTransformationLengthChange;
629     private ChangeWatcher mChangeWatcher;
630
631     private ArrayList<TextWatcher> mListeners;
632
633     // display attributes
634     private final TextPaint mTextPaint;
635     private boolean mUserSetTextScaleX;
636     private Layout mLayout;
637     private boolean mLocalesChanged = false;
638
639     // True if setKeyListener() has been explicitly called
640     private boolean mListenerChanged = false;
641     // True if internationalized input should be used for numbers and date and time.
642     private final boolean mUseInternationalizedInput;
643
644     @ViewDebug.ExportedProperty(category = "text")
645     private int mGravity = Gravity.TOP | Gravity.START;
646     private boolean mHorizontallyScrolling;
647
648     private int mAutoLinkMask;
649     private boolean mLinksClickable = true;
650
651     private float mSpacingMult = 1.0f;
652     private float mSpacingAdd = 0.0f;
653
654     private int mBreakStrategy;
655     private int mHyphenationFrequency;
656     private int mJustificationMode;
657
658     private int mMaximum = Integer.MAX_VALUE;
659     private int mMaxMode = LINES;
660     private int mMinimum = 0;
661     private int mMinMode = LINES;
662
663     private int mOldMaximum = mMaximum;
664     private int mOldMaxMode = mMaxMode;
665
666     private int mMaxWidth = Integer.MAX_VALUE;
667     private int mMaxWidthMode = PIXELS;
668     private int mMinWidth = 0;
669     private int mMinWidthMode = PIXELS;
670
671     private boolean mSingleLine;
672     private int mDesiredHeightAtMeasure = -1;
673     private boolean mIncludePad = true;
674     private int mDeferScroll = -1;
675
676     // tmp primitives, so we don't alloc them on each draw
677     private Rect mTempRect;
678     private long mLastScroll;
679     private Scroller mScroller;
680     private TextPaint mTempTextPaint;
681
682     private BoringLayout.Metrics mBoring, mHintBoring;
683     private BoringLayout mSavedLayout, mSavedHintLayout;
684
685     private TextDirectionHeuristic mTextDir;
686
687     private InputFilter[] mFilters = NO_FILTERS;
688
689     private volatile Locale mCurrentSpellCheckerLocaleCache;
690
691     // It is possible to have a selection even when mEditor is null (programmatically set, like when
692     // a link is pressed). These highlight-related fields do not go in mEditor.
693     int mHighlightColor = 0x6633B5E5;
694     private Path mHighlightPath;
695     private final Paint mHighlightPaint;
696     private boolean mHighlightPathBogus = true;
697
698     // Although these fields are specific to editable text, they are not added to Editor because
699     // they are defined by the TextView's style and are theme-dependent.
700     int mCursorDrawableRes;
701     // These six fields, could be moved to Editor, since we know their default values and we
702     // could condition the creation of the Editor to a non standard value. This is however
703     // brittle since the hardcoded values here (such as
704     // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
705     // default style is modified.
706     int mTextSelectHandleLeftRes;
707     int mTextSelectHandleRightRes;
708     int mTextSelectHandleRes;
709     int mTextEditSuggestionItemLayout;
710     int mTextEditSuggestionContainerLayout;
711     int mTextEditSuggestionHighlightStyle;
712
713     /**
714      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
715      * See {@link #createEditorIfNeeded()}.
716      */
717     private Editor mEditor;
718
719     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
720     private static final int DEVICE_PROVISIONED_NO = 1;
721     private static final int DEVICE_PROVISIONED_YES = 2;
722
723     /**
724      * Some special options such as sharing selected text should only be shown if the device
725      * is provisioned. Only check the provisioned state once for a given view instance.
726      */
727     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
728
729     /**
730      * The TextView does not auto-size text (default).
731      */
732     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
733
734     /**
735      * The TextView scales text size both horizontally and vertically to fit within the
736      * container.
737      */
738     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
739
740     /** @hide */
741     @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM})
742     @Retention(RetentionPolicy.SOURCE)
743     public @interface AutoSizeTextType {}
744     // Default minimum size for auto-sizing text in scaled pixels.
745     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
746     // Default maximum size for auto-sizing text in scaled pixels.
747     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
748     // Default value for the step size in pixels.
749     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
750     // Use this to specify that any of the auto-size configuration int values have not been set.
751     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
752     // Auto-size text type.
753     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
754     // Specify if auto-size text is needed.
755     private boolean mNeedsAutoSizeText = false;
756     // Step size for auto-sizing in pixels.
757     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
758     // Minimum text size for auto-sizing in pixels.
759     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
760     // Maximum text size for auto-sizing in pixels.
761     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
762     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
763     // when auto-sizing text.
764     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
765     // Specifies whether auto-size should use the provided auto size steps set or if it should
766     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
767     // mAutoSizeStepGranularityInPx.
768     private boolean mHasPresetAutoSizeValues = false;
769
770     // Indicates whether the text was set from resources or dynamically, so it can be used to
771     // sanitize autofill requests.
772     private boolean mTextFromResource = false;
773
774     /**
775      * Kick-start the font cache for the zygote process (to pay the cost of
776      * initializing freetype for our default font only once).
777      * @hide
778      */
779     public static void preloadFontCache() {
780         Paint p = new Paint();
781         p.setAntiAlias(true);
782         // Ensure that the Typeface is loaded here.
783         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
784         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
785         // since Paint.measureText can not be called without Typeface static initializer.
786         p.setTypeface(Typeface.DEFAULT);
787         // We don't care about the result, just the side-effect of measuring.
788         p.measureText("H");
789     }
790
791     /**
792      * Interface definition for a callback to be invoked when an action is
793      * performed on the editor.
794      */
795     public interface OnEditorActionListener {
796         /**
797          * Called when an action is being performed.
798          *
799          * @param v The view that was clicked.
800          * @param actionId Identifier of the action.  This will be either the
801          * identifier you supplied, or {@link EditorInfo#IME_NULL
802          * EditorInfo.IME_NULL} if being called due to the enter key
803          * being pressed.
804          * @param event If triggered by an enter key, this is the event;
805          * otherwise, this is null.
806          * @return Return true if you have consumed the action, else false.
807          */
808         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
809     }
810
811     public TextView(Context context) {
812         this(context, null);
813     }
814
815     public TextView(Context context, @Nullable AttributeSet attrs) {
816         this(context, attrs, com.android.internal.R.attr.textViewStyle);
817     }
818
819     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
820         this(context, attrs, defStyleAttr, 0);
821     }
822
823     @SuppressWarnings("deprecation")
824     public TextView(
825             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
826         super(context, attrs, defStyleAttr, defStyleRes);
827
828         // TextView is important by default, unless app developer overrode attribute.
829         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
830             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
831         }
832
833         mText = "";
834
835         final Resources res = getResources();
836         final CompatibilityInfo compat = res.getCompatibilityInfo();
837
838         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
839         mTextPaint.density = res.getDisplayMetrics().density;
840         mTextPaint.setCompatibilityScaling(compat.applicationScale);
841
842         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
843         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
844
845         mMovement = getDefaultMovementMethod();
846
847         mTransformation = null;
848
849         int textColorHighlight = 0;
850         ColorStateList textColor = null;
851         ColorStateList textColorHint = null;
852         ColorStateList textColorLink = null;
853         int textSize = 15;
854         String fontFamily = null;
855         Typeface fontTypeface = null;
856         boolean fontFamilyExplicit = false;
857         int typefaceIndex = -1;
858         int styleIndex = -1;
859         boolean allCaps = false;
860         int shadowcolor = 0;
861         float dx = 0, dy = 0, r = 0;
862         boolean elegant = false;
863         float letterSpacing = 0;
864         String fontFeatureSettings = null;
865         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
866         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
867         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
868
869         final Resources.Theme theme = context.getTheme();
870
871         /*
872          * Look the appearance up without checking first if it exists because
873          * almost every TextView has one and it greatly simplifies the logic
874          * to be able to parse the appearance first and then let specific tags
875          * for this View override it.
876          */
877         TypedArray a = theme.obtainStyledAttributes(attrs,
878                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
879         TypedArray appearance = null;
880         int ap = a.getResourceId(
881                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
882         a.recycle();
883         if (ap != -1) {
884             appearance = theme.obtainStyledAttributes(
885                     ap, com.android.internal.R.styleable.TextAppearance);
886         }
887         if (appearance != null) {
888             int n = appearance.getIndexCount();
889             for (int i = 0; i < n; i++) {
890                 int attr = appearance.getIndex(i);
891
892                 switch (attr) {
893                     case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
894                         textColorHighlight = appearance.getColor(attr, textColorHighlight);
895                         break;
896
897                     case com.android.internal.R.styleable.TextAppearance_textColor:
898                         textColor = appearance.getColorStateList(attr);
899                         break;
900
901                     case com.android.internal.R.styleable.TextAppearance_textColorHint:
902                         textColorHint = appearance.getColorStateList(attr);
903                         break;
904
905                     case com.android.internal.R.styleable.TextAppearance_textColorLink:
906                         textColorLink = appearance.getColorStateList(attr);
907                         break;
908
909                     case com.android.internal.R.styleable.TextAppearance_textSize:
910                         textSize = appearance.getDimensionPixelSize(attr, textSize);
911                         break;
912
913                     case com.android.internal.R.styleable.TextAppearance_typeface:
914                         typefaceIndex = appearance.getInt(attr, -1);
915                         break;
916
917                     case com.android.internal.R.styleable.TextAppearance_fontFamily:
918                         if (!context.isRestricted() && context.canLoadUnsafeResources()) {
919                             try {
920                                 fontTypeface = appearance.getFont(attr);
921                             } catch (UnsupportedOperationException
922                                     | Resources.NotFoundException e) {
923                                 // Expected if it is not a font resource.
924                             }
925                         }
926                         if (fontTypeface == null) {
927                             fontFamily = appearance.getString(attr);
928                         }
929                         break;
930
931                     case com.android.internal.R.styleable.TextAppearance_textStyle:
932                         styleIndex = appearance.getInt(attr, -1);
933                         break;
934
935                     case com.android.internal.R.styleable.TextAppearance_textAllCaps:
936                         allCaps = appearance.getBoolean(attr, false);
937                         break;
938
939                     case com.android.internal.R.styleable.TextAppearance_shadowColor:
940                         shadowcolor = appearance.getInt(attr, 0);
941                         break;
942
943                     case com.android.internal.R.styleable.TextAppearance_shadowDx:
944                         dx = appearance.getFloat(attr, 0);
945                         break;
946
947                     case com.android.internal.R.styleable.TextAppearance_shadowDy:
948                         dy = appearance.getFloat(attr, 0);
949                         break;
950
951                     case com.android.internal.R.styleable.TextAppearance_shadowRadius:
952                         r = appearance.getFloat(attr, 0);
953                         break;
954
955                     case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
956                         elegant = appearance.getBoolean(attr, false);
957                         break;
958
959                     case com.android.internal.R.styleable.TextAppearance_letterSpacing:
960                         letterSpacing = appearance.getFloat(attr, 0);
961                         break;
962
963                     case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
964                         fontFeatureSettings = appearance.getString(attr);
965                         break;
966                 }
967             }
968
969             appearance.recycle();
970         }
971
972         boolean editable = getDefaultEditable();
973         CharSequence inputMethod = null;
974         int numeric = 0;
975         CharSequence digits = null;
976         boolean phone = false;
977         boolean autotext = false;
978         int autocap = -1;
979         int buffertype = 0;
980         boolean selectallonfocus = false;
981         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
982                 drawableBottom = null, drawableStart = null, drawableEnd = null;
983         ColorStateList drawableTint = null;
984         PorterDuff.Mode drawableTintMode = null;
985         int drawablePadding = 0;
986         int ellipsize = -1;
987         boolean singleLine = false;
988         int maxlength = -1;
989         CharSequence text = "";
990         CharSequence hint = null;
991         boolean password = false;
992         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
993         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
994         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
995         int inputType = EditorInfo.TYPE_NULL;
996         a = theme.obtainStyledAttributes(
997                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
998
999         int n = a.getIndexCount();
1000
1001         boolean fromResourceId = false;
1002         for (int i = 0; i < n; i++) {
1003             int attr = a.getIndex(i);
1004
1005             switch (attr) {
1006                 case com.android.internal.R.styleable.TextView_editable:
1007                     editable = a.getBoolean(attr, editable);
1008                     break;
1009
1010                 case com.android.internal.R.styleable.TextView_inputMethod:
1011                     inputMethod = a.getText(attr);
1012                     break;
1013
1014                 case com.android.internal.R.styleable.TextView_numeric:
1015                     numeric = a.getInt(attr, numeric);
1016                     break;
1017
1018                 case com.android.internal.R.styleable.TextView_digits:
1019                     digits = a.getText(attr);
1020                     break;
1021
1022                 case com.android.internal.R.styleable.TextView_phoneNumber:
1023                     phone = a.getBoolean(attr, phone);
1024                     break;
1025
1026                 case com.android.internal.R.styleable.TextView_autoText:
1027                     autotext = a.getBoolean(attr, autotext);
1028                     break;
1029
1030                 case com.android.internal.R.styleable.TextView_capitalize:
1031                     autocap = a.getInt(attr, autocap);
1032                     break;
1033
1034                 case com.android.internal.R.styleable.TextView_bufferType:
1035                     buffertype = a.getInt(attr, buffertype);
1036                     break;
1037
1038                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1039                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
1040                     break;
1041
1042                 case com.android.internal.R.styleable.TextView_autoLink:
1043                     mAutoLinkMask = a.getInt(attr, 0);
1044                     break;
1045
1046                 case com.android.internal.R.styleable.TextView_linksClickable:
1047                     mLinksClickable = a.getBoolean(attr, true);
1048                     break;
1049
1050                 case com.android.internal.R.styleable.TextView_drawableLeft:
1051                     drawableLeft = a.getDrawable(attr);
1052                     break;
1053
1054                 case com.android.internal.R.styleable.TextView_drawableTop:
1055                     drawableTop = a.getDrawable(attr);
1056                     break;
1057
1058                 case com.android.internal.R.styleable.TextView_drawableRight:
1059                     drawableRight = a.getDrawable(attr);
1060                     break;
1061
1062                 case com.android.internal.R.styleable.TextView_drawableBottom:
1063                     drawableBottom = a.getDrawable(attr);
1064                     break;
1065
1066                 case com.android.internal.R.styleable.TextView_drawableStart:
1067                     drawableStart = a.getDrawable(attr);
1068                     break;
1069
1070                 case com.android.internal.R.styleable.TextView_drawableEnd:
1071                     drawableEnd = a.getDrawable(attr);
1072                     break;
1073
1074                 case com.android.internal.R.styleable.TextView_drawableTint:
1075                     drawableTint = a.getColorStateList(attr);
1076                     break;
1077
1078                 case com.android.internal.R.styleable.TextView_drawableTintMode:
1079                     drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
1080                     break;
1081
1082                 case com.android.internal.R.styleable.TextView_drawablePadding:
1083                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1084                     break;
1085
1086                 case com.android.internal.R.styleable.TextView_maxLines:
1087                     setMaxLines(a.getInt(attr, -1));
1088                     break;
1089
1090                 case com.android.internal.R.styleable.TextView_maxHeight:
1091                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
1092                     break;
1093
1094                 case com.android.internal.R.styleable.TextView_lines:
1095                     setLines(a.getInt(attr, -1));
1096                     break;
1097
1098                 case com.android.internal.R.styleable.TextView_height:
1099                     setHeight(a.getDimensionPixelSize(attr, -1));
1100                     break;
1101
1102                 case com.android.internal.R.styleable.TextView_minLines:
1103                     setMinLines(a.getInt(attr, -1));
1104                     break;
1105
1106                 case com.android.internal.R.styleable.TextView_minHeight:
1107                     setMinHeight(a.getDimensionPixelSize(attr, -1));
1108                     break;
1109
1110                 case com.android.internal.R.styleable.TextView_maxEms:
1111                     setMaxEms(a.getInt(attr, -1));
1112                     break;
1113
1114                 case com.android.internal.R.styleable.TextView_maxWidth:
1115                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
1116                     break;
1117
1118                 case com.android.internal.R.styleable.TextView_ems:
1119                     setEms(a.getInt(attr, -1));
1120                     break;
1121
1122                 case com.android.internal.R.styleable.TextView_width:
1123                     setWidth(a.getDimensionPixelSize(attr, -1));
1124                     break;
1125
1126                 case com.android.internal.R.styleable.TextView_minEms:
1127                     setMinEms(a.getInt(attr, -1));
1128                     break;
1129
1130                 case com.android.internal.R.styleable.TextView_minWidth:
1131                     setMinWidth(a.getDimensionPixelSize(attr, -1));
1132                     break;
1133
1134                 case com.android.internal.R.styleable.TextView_gravity:
1135                     setGravity(a.getInt(attr, -1));
1136                     break;
1137
1138                 case com.android.internal.R.styleable.TextView_hint:
1139                     hint = a.getText(attr);
1140                     break;
1141
1142                 case com.android.internal.R.styleable.TextView_text:
1143                     fromResourceId = true;
1144                     text = a.getText(attr);
1145                     break;
1146
1147                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1148                     if (a.getBoolean(attr, false)) {
1149                         setHorizontallyScrolling(true);
1150                     }
1151                     break;
1152
1153                 case com.android.internal.R.styleable.TextView_singleLine:
1154                     singleLine = a.getBoolean(attr, singleLine);
1155                     break;
1156
1157                 case com.android.internal.R.styleable.TextView_ellipsize:
1158                     ellipsize = a.getInt(attr, ellipsize);
1159                     break;
1160
1161                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1162                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1163                     break;
1164
1165                 case com.android.internal.R.styleable.TextView_includeFontPadding:
1166                     if (!a.getBoolean(attr, true)) {
1167                         setIncludeFontPadding(false);
1168                     }
1169                     break;
1170
1171                 case com.android.internal.R.styleable.TextView_cursorVisible:
1172                     if (!a.getBoolean(attr, true)) {
1173                         setCursorVisible(false);
1174                     }
1175                     break;
1176
1177                 case com.android.internal.R.styleable.TextView_maxLength:
1178                     maxlength = a.getInt(attr, -1);
1179                     break;
1180
1181                 case com.android.internal.R.styleable.TextView_textScaleX:
1182                     setTextScaleX(a.getFloat(attr, 1.0f));
1183                     break;
1184
1185                 case com.android.internal.R.styleable.TextView_freezesText:
1186                     mFreezesText = a.getBoolean(attr, false);
1187                     break;
1188
1189                 case com.android.internal.R.styleable.TextView_shadowColor:
1190                     shadowcolor = a.getInt(attr, 0);
1191                     break;
1192
1193                 case com.android.internal.R.styleable.TextView_shadowDx:
1194                     dx = a.getFloat(attr, 0);
1195                     break;
1196
1197                 case com.android.internal.R.styleable.TextView_shadowDy:
1198                     dy = a.getFloat(attr, 0);
1199                     break;
1200
1201                 case com.android.internal.R.styleable.TextView_shadowRadius:
1202                     r = a.getFloat(attr, 0);
1203                     break;
1204
1205                 case com.android.internal.R.styleable.TextView_enabled:
1206                     setEnabled(a.getBoolean(attr, isEnabled()));
1207                     break;
1208
1209                 case com.android.internal.R.styleable.TextView_textColorHighlight:
1210                     textColorHighlight = a.getColor(attr, textColorHighlight);
1211                     break;
1212
1213                 case com.android.internal.R.styleable.TextView_textColor:
1214                     textColor = a.getColorStateList(attr);
1215                     break;
1216
1217                 case com.android.internal.R.styleable.TextView_textColorHint:
1218                     textColorHint = a.getColorStateList(attr);
1219                     break;
1220
1221                 case com.android.internal.R.styleable.TextView_textColorLink:
1222                     textColorLink = a.getColorStateList(attr);
1223                     break;
1224
1225                 case com.android.internal.R.styleable.TextView_textSize:
1226                     textSize = a.getDimensionPixelSize(attr, textSize);
1227                     break;
1228
1229                 case com.android.internal.R.styleable.TextView_typeface:
1230                     typefaceIndex = a.getInt(attr, typefaceIndex);
1231                     break;
1232
1233                 case com.android.internal.R.styleable.TextView_textStyle:
1234                     styleIndex = a.getInt(attr, styleIndex);
1235                     break;
1236
1237                 case com.android.internal.R.styleable.TextView_fontFamily:
1238                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
1239                         try {
1240                             fontTypeface = a.getFont(attr);
1241                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
1242                             // Expected if it is not a resource reference or if it is a reference to
1243                             // another resource type.
1244                         }
1245                     }
1246                     if (fontTypeface == null) {
1247                         fontFamily = a.getString(attr);
1248                     }
1249                     fontFamilyExplicit = true;
1250                     break;
1251
1252                 case com.android.internal.R.styleable.TextView_password:
1253                     password = a.getBoolean(attr, password);
1254                     break;
1255
1256                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1257                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1258                     break;
1259
1260                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1261                     mSpacingMult = a.getFloat(attr, mSpacingMult);
1262                     break;
1263
1264                 case com.android.internal.R.styleable.TextView_inputType:
1265                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1266                     break;
1267
1268                 case com.android.internal.R.styleable.TextView_allowUndo:
1269                     createEditorIfNeeded();
1270                     mEditor.mAllowUndo = a.getBoolean(attr, true);
1271                     break;
1272
1273                 case com.android.internal.R.styleable.TextView_imeOptions:
1274                     createEditorIfNeeded();
1275                     mEditor.createInputContentTypeIfNeeded();
1276                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
1277                             mEditor.mInputContentType.imeOptions);
1278                     break;
1279
1280                 case com.android.internal.R.styleable.TextView_imeActionLabel:
1281                     createEditorIfNeeded();
1282                     mEditor.createInputContentTypeIfNeeded();
1283                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1284                     break;
1285
1286                 case com.android.internal.R.styleable.TextView_imeActionId:
1287                     createEditorIfNeeded();
1288                     mEditor.createInputContentTypeIfNeeded();
1289                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
1290                             mEditor.mInputContentType.imeActionId);
1291                     break;
1292
1293                 case com.android.internal.R.styleable.TextView_privateImeOptions:
1294                     setPrivateImeOptions(a.getString(attr));
1295                     break;
1296
1297                 case com.android.internal.R.styleable.TextView_editorExtras:
1298                     try {
1299                         setInputExtras(a.getResourceId(attr, 0));
1300                     } catch (XmlPullParserException e) {
1301                         Log.w(LOG_TAG, "Failure reading input extras", e);
1302                     } catch (IOException e) {
1303                         Log.w(LOG_TAG, "Failure reading input extras", e);
1304                     }
1305                     break;
1306
1307                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1308                     mCursorDrawableRes = a.getResourceId(attr, 0);
1309                     break;
1310
1311                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1312                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1313                     break;
1314
1315                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1316                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1317                     break;
1318
1319                 case com.android.internal.R.styleable.TextView_textSelectHandle:
1320                     mTextSelectHandleRes = a.getResourceId(attr, 0);
1321                     break;
1322
1323                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1324                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1325                     break;
1326
1327                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1328                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1329                     break;
1330
1331                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1332                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1333                     break;
1334
1335                 case com.android.internal.R.styleable.TextView_textIsSelectable:
1336                     setTextIsSelectable(a.getBoolean(attr, false));
1337                     break;
1338
1339                 case com.android.internal.R.styleable.TextView_textAllCaps:
1340                     allCaps = a.getBoolean(attr, false);
1341                     break;
1342
1343                 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1344                     elegant = a.getBoolean(attr, false);
1345                     break;
1346
1347                 case com.android.internal.R.styleable.TextView_letterSpacing:
1348                     letterSpacing = a.getFloat(attr, 0);
1349                     break;
1350
1351                 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1352                     fontFeatureSettings = a.getString(attr);
1353                     break;
1354
1355                 case com.android.internal.R.styleable.TextView_breakStrategy:
1356                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1357                     break;
1358
1359                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1360                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1361                     break;
1362
1363                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1364                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1365                     break;
1366
1367                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1368                     autoSizeStepGranularityInPx = a.getDimension(attr,
1369                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1370                     break;
1371
1372                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1373                     autoSizeMinTextSizeInPx = a.getDimension(attr,
1374                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1375                     break;
1376
1377                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1378                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
1379                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1380                     break;
1381
1382                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1383                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1384                     if (autoSizeStepSizeArrayResId > 0) {
1385                         final TypedArray autoSizePresetTextSizes = a.getResources()
1386                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
1387                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1388                         autoSizePresetTextSizes.recycle();
1389                     }
1390                     break;
1391                 case com.android.internal.R.styleable.TextView_justificationMode:
1392                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1393                     break;
1394             }
1395         }
1396
1397         a.recycle();
1398
1399         BufferType bufferType = BufferType.EDITABLE;
1400
1401         final int variation =
1402                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1403         final boolean passwordInputType = variation
1404                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1405         final boolean webPasswordInputType = variation
1406                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1407         final boolean numberPasswordInputType = variation
1408                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1409
1410         mUseInternationalizedInput =
1411                 context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.O;
1412
1413         if (inputMethod != null) {
1414             Class<?> c;
1415
1416             try {
1417                 c = Class.forName(inputMethod.toString());
1418             } catch (ClassNotFoundException ex) {
1419                 throw new RuntimeException(ex);
1420             }
1421
1422             try {
1423                 createEditorIfNeeded();
1424                 mEditor.mKeyListener = (KeyListener) c.newInstance();
1425             } catch (InstantiationException ex) {
1426                 throw new RuntimeException(ex);
1427             } catch (IllegalAccessException ex) {
1428                 throw new RuntimeException(ex);
1429             }
1430             try {
1431                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1432                         ? inputType
1433                         : mEditor.mKeyListener.getInputType();
1434             } catch (IncompatibleClassChangeError e) {
1435                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1436             }
1437         } else if (digits != null) {
1438             createEditorIfNeeded();
1439             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1440             // If no input type was specified, we will default to generic
1441             // text, since we can't tell the IME about the set of digits
1442             // that was selected.
1443             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1444                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1445         } else if (inputType != EditorInfo.TYPE_NULL) {
1446             setInputType(inputType, true);
1447             // If set, the input type overrides what was set using the deprecated singleLine flag.
1448             singleLine = !isMultilineInputType(inputType);
1449         } else if (phone) {
1450             createEditorIfNeeded();
1451             mEditor.mKeyListener = DialerKeyListener.getInstance();
1452             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1453         } else if (numeric != 0) {
1454             createEditorIfNeeded();
1455             mEditor.mKeyListener = DigitsKeyListener.getInstance(
1456                     null,  // locale
1457                     (numeric & SIGNED) != 0,
1458                     (numeric & DECIMAL) != 0);
1459             inputType = mEditor.mKeyListener.getInputType();
1460             mEditor.mInputType = inputType;
1461         } else if (autotext || autocap != -1) {
1462             TextKeyListener.Capitalize cap;
1463
1464             inputType = EditorInfo.TYPE_CLASS_TEXT;
1465
1466             switch (autocap) {
1467                 case 1:
1468                     cap = TextKeyListener.Capitalize.SENTENCES;
1469                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1470                     break;
1471
1472                 case 2:
1473                     cap = TextKeyListener.Capitalize.WORDS;
1474                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1475                     break;
1476
1477                 case 3:
1478                     cap = TextKeyListener.Capitalize.CHARACTERS;
1479                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1480                     break;
1481
1482                 default:
1483                     cap = TextKeyListener.Capitalize.NONE;
1484                     break;
1485             }
1486
1487             createEditorIfNeeded();
1488             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1489             mEditor.mInputType = inputType;
1490         } else if (editable) {
1491             createEditorIfNeeded();
1492             mEditor.mKeyListener = TextKeyListener.getInstance();
1493             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1494         } else if (isTextSelectable()) {
1495             // Prevent text changes from keyboard.
1496             if (mEditor != null) {
1497                 mEditor.mKeyListener = null;
1498                 mEditor.mInputType = EditorInfo.TYPE_NULL;
1499             }
1500             bufferType = BufferType.SPANNABLE;
1501             // So that selection can be changed using arrow keys and touch is handled.
1502             setMovementMethod(ArrowKeyMovementMethod.getInstance());
1503         } else {
1504             if (mEditor != null) mEditor.mKeyListener = null;
1505
1506             switch (buffertype) {
1507                 case 0:
1508                     bufferType = BufferType.NORMAL;
1509                     break;
1510                 case 1:
1511                     bufferType = BufferType.SPANNABLE;
1512                     break;
1513                 case 2:
1514                     bufferType = BufferType.EDITABLE;
1515                     break;
1516             }
1517         }
1518
1519         if (mEditor != null) {
1520             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1521                     numberPasswordInputType);
1522         }
1523
1524         if (selectallonfocus) {
1525             createEditorIfNeeded();
1526             mEditor.mSelectAllOnFocus = true;
1527
1528             if (bufferType == BufferType.NORMAL) {
1529                 bufferType = BufferType.SPANNABLE;
1530             }
1531         }
1532
1533         // Set up the tint (if needed) before setting the drawables so that it
1534         // gets applied correctly.
1535         if (drawableTint != null || drawableTintMode != null) {
1536             if (mDrawables == null) {
1537                 mDrawables = new Drawables(context);
1538             }
1539             if (drawableTint != null) {
1540                 mDrawables.mTintList = drawableTint;
1541                 mDrawables.mHasTint = true;
1542             }
1543             if (drawableTintMode != null) {
1544                 mDrawables.mTintMode = drawableTintMode;
1545                 mDrawables.mHasTintMode = true;
1546             }
1547         }
1548
1549         // This call will save the initial left/right drawables
1550         setCompoundDrawablesWithIntrinsicBounds(
1551                 drawableLeft, drawableTop, drawableRight, drawableBottom);
1552         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1553         setCompoundDrawablePadding(drawablePadding);
1554
1555         // Same as setSingleLine(), but make sure the transformation method and the maximum number
1556         // of lines of height are unchanged for multi-line TextViews.
1557         setInputTypeSingleLine(singleLine);
1558         applySingleLine(singleLine, singleLine, singleLine);
1559
1560         if (singleLine && getKeyListener() == null && ellipsize < 0) {
1561             ellipsize = 3; // END
1562         }
1563
1564         switch (ellipsize) {
1565             case 1:
1566                 setEllipsize(TextUtils.TruncateAt.START);
1567                 break;
1568             case 2:
1569                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1570                 break;
1571             case 3:
1572                 setEllipsize(TextUtils.TruncateAt.END);
1573                 break;
1574             case 4:
1575                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1576                     setHorizontalFadingEdgeEnabled(true);
1577                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1578                 } else {
1579                     setHorizontalFadingEdgeEnabled(false);
1580                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1581                 }
1582                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1583                 break;
1584         }
1585
1586         setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1587         setHintTextColor(textColorHint);
1588         setLinkTextColor(textColorLink);
1589         if (textColorHighlight != 0) {
1590             setHighlightColor(textColorHighlight);
1591         }
1592         setRawTextSize(textSize, true /* shouldRequestLayout */);
1593         setElegantTextHeight(elegant);
1594         setLetterSpacing(letterSpacing);
1595         setFontFeatureSettings(fontFeatureSettings);
1596
1597         if (allCaps) {
1598             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1599         }
1600
1601         if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1602             setTransformationMethod(PasswordTransformationMethod.getInstance());
1603             typefaceIndex = MONOSPACE;
1604         } else if (mEditor != null
1605                 && (mEditor.mInputType
1606                         & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1607                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1608             typefaceIndex = MONOSPACE;
1609         }
1610
1611         if (typefaceIndex != -1 && !fontFamilyExplicit) {
1612             fontFamily = null;
1613         }
1614         setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
1615
1616         if (shadowcolor != 0) {
1617             setShadowLayer(r, dx, dy, shadowcolor);
1618         }
1619
1620         if (maxlength >= 0) {
1621             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1622         } else {
1623             setFilters(NO_FILTERS);
1624         }
1625
1626         setText(text, bufferType);
1627         if (fromResourceId) {
1628             mTextFromResource = true;
1629         }
1630
1631         if (hint != null) setHint(hint);
1632
1633         /*
1634          * Views are not normally clickable unless specified to be.
1635          * However, TextViews that have input or movement methods *are*
1636          * clickable by default. By setting clickable here, we implicitly set focusable as well
1637          * if not overridden by the developer.
1638          */
1639         a = context.obtainStyledAttributes(
1640                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1641         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1642         boolean clickable = canInputOrMove || isClickable();
1643         boolean longClickable = canInputOrMove || isLongClickable();
1644         int focusable = getFocusable();
1645
1646         n = a.getIndexCount();
1647         for (int i = 0; i < n; i++) {
1648             int attr = a.getIndex(i);
1649
1650             switch (attr) {
1651                 case com.android.internal.R.styleable.View_focusable:
1652                     TypedValue val = new TypedValue();
1653                     if (a.getValue(attr, val)) {
1654                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1655                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1656                                 : val.data;
1657                     }
1658                     break;
1659
1660                 case com.android.internal.R.styleable.View_clickable:
1661                     clickable = a.getBoolean(attr, clickable);
1662                     break;
1663
1664                 case com.android.internal.R.styleable.View_longClickable:
1665                     longClickable = a.getBoolean(attr, longClickable);
1666                     break;
1667             }
1668         }
1669         a.recycle();
1670
1671         // Some apps were relying on the undefined behavior of focusable winning over
1672         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1673         // when starting with EditText and setting only focusable=false). To keep those apps from
1674         // breaking, re-apply the focusable attribute here.
1675         if (focusable != getFocusable()) {
1676             setFocusable(focusable);
1677         }
1678         setClickable(clickable);
1679         setLongClickable(longClickable);
1680
1681         if (mEditor != null) mEditor.prepareCursorControllers();
1682
1683         // If not explicitly specified this view is important for accessibility.
1684         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1685             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1686         }
1687
1688         if (supportsAutoSizeText()) {
1689             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1690                 // If uniform auto-size has been specified but preset values have not been set then
1691                 // replace the auto-size configuration values that have not been specified with the
1692                 // defaults.
1693                 if (!mHasPresetAutoSizeValues) {
1694                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1695
1696                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1697                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1698                                 TypedValue.COMPLEX_UNIT_SP,
1699                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1700                                 displayMetrics);
1701                     }
1702
1703                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1704                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1705                                 TypedValue.COMPLEX_UNIT_SP,
1706                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1707                                 displayMetrics);
1708                     }
1709
1710                     if (autoSizeStepGranularityInPx
1711                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1712                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1713                     }
1714
1715                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1716                             autoSizeMaxTextSizeInPx,
1717                             autoSizeStepGranularityInPx);
1718                 }
1719
1720                 setupAutoSizeText();
1721             }
1722         } else {
1723             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1724         }
1725     }
1726
1727     /**
1728      * Specify whether this widget should automatically scale the text to try to perfectly fit
1729      * within the layout bounds by using the default auto-size configuration.
1730      *
1731      * @param autoSizeTextType the type of auto-size. Must be one of
1732      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1733      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1734      *
1735      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1736      *
1737      * @attr ref android.R.styleable#TextView_autoSizeTextType
1738      *
1739      * @see #getAutoSizeTextType()
1740      */
1741     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1742         if (supportsAutoSizeText()) {
1743             switch (autoSizeTextType) {
1744                 case AUTO_SIZE_TEXT_TYPE_NONE:
1745                     clearAutoSizeConfiguration();
1746                     break;
1747                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1748                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1749                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1750                             TypedValue.COMPLEX_UNIT_SP,
1751                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1752                             displayMetrics);
1753                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1754                             TypedValue.COMPLEX_UNIT_SP,
1755                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1756                             displayMetrics);
1757
1758                     validateAndSetAutoSizeTextTypeUniformConfiguration(
1759                             autoSizeMinTextSizeInPx,
1760                             autoSizeMaxTextSizeInPx,
1761                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1762                     if (setupAutoSizeText()) {
1763                         autoSizeText();
1764                         invalidate();
1765                     }
1766                     break;
1767                 default:
1768                     throw new IllegalArgumentException(
1769                             "Unknown auto-size text type: " + autoSizeTextType);
1770             }
1771         }
1772     }
1773
1774     /**
1775      * Specify whether this widget should automatically scale the text to try to perfectly fit
1776      * within the layout bounds. If all the configuration params are valid the type of auto-size is
1777      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1778      *
1779      * @param autoSizeMinTextSize the minimum text size available for auto-size
1780      * @param autoSizeMaxTextSize the maximum text size available for auto-size
1781      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1782      *                                the minimum and maximum text size in order to build the set of
1783      *                                text sizes the system uses to choose from when auto-sizing
1784      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1785      *             possible dimension units
1786      *
1787      * @throws IllegalArgumentException if any of the configuration params are invalid.
1788      *
1789      * @attr ref android.R.styleable#TextView_autoSizeTextType
1790      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1791      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1792      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1793      *
1794      * @see #setAutoSizeTextTypeWithDefaults(int)
1795      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1796      * @see #getAutoSizeMinTextSize()
1797      * @see #getAutoSizeMaxTextSize()
1798      * @see #getAutoSizeStepGranularity()
1799      * @see #getAutoSizeTextAvailableSizes()
1800      */
1801     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1802             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1803         if (supportsAutoSizeText()) {
1804             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1805             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1806                     unit, autoSizeMinTextSize, displayMetrics);
1807             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1808                     unit, autoSizeMaxTextSize, displayMetrics);
1809             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1810                     unit, autoSizeStepGranularity, displayMetrics);
1811
1812             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1813                     autoSizeMaxTextSizeInPx,
1814                     autoSizeStepGranularityInPx);
1815
1816             if (setupAutoSizeText()) {
1817                 autoSizeText();
1818                 invalidate();
1819             }
1820         }
1821     }
1822
1823     /**
1824      * Specify whether this widget should automatically scale the text to try to perfectly fit
1825      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1826      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1827      *
1828      * @param presetSizes an {@code int} array of sizes in pixels
1829      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1830      *             the possible dimension units
1831      *
1832      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1833      *
1834      * @attr ref android.R.styleable#TextView_autoSizeTextType
1835      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1836      *
1837      * @see #setAutoSizeTextTypeWithDefaults(int)
1838      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1839      * @see #getAutoSizeMinTextSize()
1840      * @see #getAutoSizeMaxTextSize()
1841      * @see #getAutoSizeTextAvailableSizes()
1842      */
1843     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1844         if (supportsAutoSizeText()) {
1845             final int presetSizesLength = presetSizes.length;
1846             if (presetSizesLength > 0) {
1847                 int[] presetSizesInPx = new int[presetSizesLength];
1848
1849                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1850                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1851                 } else {
1852                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1853                     // Convert all to sizes to pixels.
1854                     for (int i = 0; i < presetSizesLength; i++) {
1855                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1856                             presetSizes[i], displayMetrics));
1857                     }
1858                 }
1859
1860                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1861                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1862                     throw new IllegalArgumentException("None of the preset sizes is valid: "
1863                             + Arrays.toString(presetSizes));
1864                 }
1865             } else {
1866                 mHasPresetAutoSizeValues = false;
1867             }
1868
1869             if (setupAutoSizeText()) {
1870                 autoSizeText();
1871                 invalidate();
1872             }
1873         }
1874     }
1875
1876     /**
1877      * Returns the type of auto-size set for this widget.
1878      *
1879      * @return an {@code int} corresponding to one of the auto-size types:
1880      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1881      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1882      *
1883      * @attr ref android.R.styleable#TextView_autoSizeTextType
1884      *
1885      * @see #setAutoSizeTextTypeWithDefaults(int)
1886      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1887      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1888      */
1889     @AutoSizeTextType
1890     public int getAutoSizeTextType() {
1891         return mAutoSizeTextType;
1892     }
1893
1894     /**
1895      * @return the current auto-size step granularity in pixels.
1896      *
1897      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1898      *
1899      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1900      */
1901     public int getAutoSizeStepGranularity() {
1902         return Math.round(mAutoSizeStepGranularityInPx);
1903     }
1904
1905     /**
1906      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1907      *         if auto-size has not been configured this function returns {@code -1}.
1908      *
1909      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1910      *
1911      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1912      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1913      */
1914     public int getAutoSizeMinTextSize() {
1915         return Math.round(mAutoSizeMinTextSizeInPx);
1916     }
1917
1918     /**
1919      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1920      *         if auto-size has not been configured this function returns {@code -1}.
1921      *
1922      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1923      *
1924      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1925      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1926      */
1927     public int getAutoSizeMaxTextSize() {
1928         return Math.round(mAutoSizeMaxTextSizeInPx);
1929     }
1930
1931     /**
1932      * @return the current auto-size {@code int} sizes array (in pixels).
1933      *
1934      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1935      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1936      */
1937     public int[] getAutoSizeTextAvailableSizes() {
1938         return mAutoSizeTextSizesInPx;
1939     }
1940
1941     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1942         final int textSizesLength = textSizes.length();
1943         final int[] parsedSizes = new int[textSizesLength];
1944
1945         if (textSizesLength > 0) {
1946             for (int i = 0; i < textSizesLength; i++) {
1947                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1948             }
1949             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1950             setupAutoSizeUniformPresetSizesConfiguration();
1951         }
1952     }
1953
1954     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
1955         final int sizesLength = mAutoSizeTextSizesInPx.length;
1956         mHasPresetAutoSizeValues = sizesLength > 0;
1957         if (mHasPresetAutoSizeValues) {
1958             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1959             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1960             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1961             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1962         }
1963         return mHasPresetAutoSizeValues;
1964     }
1965
1966     /**
1967      * If all params are valid then save the auto-size configuration.
1968      *
1969      * @throws IllegalArgumentException if any of the params are invalid
1970      */
1971     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
1972             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
1973         // First validate.
1974         if (autoSizeMinTextSizeInPx <= 0) {
1975             throw new IllegalArgumentException("Minimum auto-size text size ("
1976                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
1977         }
1978
1979         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
1980             throw new IllegalArgumentException("Maximum auto-size text size ("
1981                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
1982                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
1983         }
1984
1985         if (autoSizeStepGranularityInPx <= 0) {
1986             throw new IllegalArgumentException("The auto-size step granularity ("
1987                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
1988         }
1989
1990         // All good, persist the configuration.
1991         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1992         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
1993         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
1994         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
1995         mHasPresetAutoSizeValues = false;
1996     }
1997
1998     private void clearAutoSizeConfiguration() {
1999         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2000         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2001         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2002         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2003         mAutoSizeTextSizesInPx = EmptyArray.INT;
2004         mNeedsAutoSizeText = false;
2005     }
2006
2007     // Returns distinct sorted positive values.
2008     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2009         final int presetValuesLength = presetValues.length;
2010         if (presetValuesLength == 0) {
2011             return presetValues;
2012         }
2013         Arrays.sort(presetValues);
2014
2015         final IntArray uniqueValidSizes = new IntArray();
2016         for (int i = 0; i < presetValuesLength; i++) {
2017             final int currentPresetValue = presetValues[i];
2018
2019             if (currentPresetValue > 0
2020                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2021                 uniqueValidSizes.add(currentPresetValue);
2022             }
2023         }
2024
2025         return presetValuesLength == uniqueValidSizes.size()
2026             ? presetValues
2027             : uniqueValidSizes.toArray();
2028     }
2029
2030     private boolean setupAutoSizeText() {
2031         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2032             // Calculate the sizes set based on minimum size, maximum size and step size if we do
2033             // not have a predefined set of sizes or if the current sizes array is empty.
2034             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2035                 int autoSizeValuesLength = 1;
2036                 float currentSize = Math.round(mAutoSizeMinTextSizeInPx);
2037                 while (Math.round(currentSize + mAutoSizeStepGranularityInPx)
2038                         <= Math.round(mAutoSizeMaxTextSizeInPx)) {
2039                     autoSizeValuesLength++;
2040                     currentSize += mAutoSizeStepGranularityInPx;
2041                 }
2042
2043                 int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2044                 float sizeToAdd = mAutoSizeMinTextSizeInPx;
2045                 for (int i = 0; i < autoSizeValuesLength; i++) {
2046                     autoSizeTextSizesInPx[i] = Math.round(sizeToAdd);
2047                     sizeToAdd += mAutoSizeStepGranularityInPx;
2048                 }
2049                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2050             }
2051
2052             mNeedsAutoSizeText = true;
2053         } else {
2054             mNeedsAutoSizeText = false;
2055         }
2056
2057         return mNeedsAutoSizeText;
2058     }
2059
2060     private int[] parseDimensionArray(TypedArray dimens) {
2061         if (dimens == null) {
2062             return null;
2063         }
2064         int[] result = new int[dimens.length()];
2065         for (int i = 0; i < result.length; i++) {
2066             result[i] = dimens.getDimensionPixelSize(i, 0);
2067         }
2068         return result;
2069     }
2070
2071     /**
2072      * @hide
2073      */
2074     @Override
2075     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2076         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2077             if (resultCode == Activity.RESULT_OK && data != null) {
2078                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2079                 if (result != null) {
2080                     if (isTextEditable()) {
2081                         replaceSelectionWithText(result);
2082                         if (mEditor != null) {
2083                             mEditor.refreshTextActionMode();
2084                         }
2085                     } else {
2086                         if (result.length() > 0) {
2087                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2088                                 .show();
2089                         }
2090                     }
2091                 }
2092             } else if (mText instanceof Spannable) {
2093                 // Reset the selection.
2094                 Selection.setSelection((Spannable) mText, getSelectionEnd());
2095             }
2096         }
2097     }
2098
2099     private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex,
2100             int styleIndex) {
2101         Typeface tf = fontTypeface;
2102         if (tf == null && familyName != null) {
2103             tf = Typeface.create(familyName, styleIndex);
2104         } else if (tf != null && tf.getStyle() != styleIndex) {
2105             tf = Typeface.create(tf, styleIndex);
2106         }
2107         if (tf != null) {
2108             setTypeface(tf);
2109             return;
2110         }
2111         switch (typefaceIndex) {
2112             case SANS:
2113                 tf = Typeface.SANS_SERIF;
2114                 break;
2115
2116             case SERIF:
2117                 tf = Typeface.SERIF;
2118                 break;
2119
2120             case MONOSPACE:
2121                 tf = Typeface.MONOSPACE;
2122                 break;
2123         }
2124
2125         setTypeface(tf, styleIndex);
2126     }
2127
2128     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2129         boolean hasRelativeDrawables = (start != null) || (end != null);
2130         if (hasRelativeDrawables) {
2131             Drawables dr = mDrawables;
2132             if (dr == null) {
2133                 mDrawables = dr = new Drawables(getContext());
2134             }
2135             mDrawables.mOverride = true;
2136             final Rect compoundRect = dr.mCompoundRect;
2137             int[] state = getDrawableState();
2138             if (start != null) {
2139                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2140                 start.setState(state);
2141                 start.copyBounds(compoundRect);
2142                 start.setCallback(this);
2143
2144                 dr.mDrawableStart = start;
2145                 dr.mDrawableSizeStart = compoundRect.width();
2146                 dr.mDrawableHeightStart = compoundRect.height();
2147             } else {
2148                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2149             }
2150             if (end != null) {
2151                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2152                 end.setState(state);
2153                 end.copyBounds(compoundRect);
2154                 end.setCallback(this);
2155
2156                 dr.mDrawableEnd = end;
2157                 dr.mDrawableSizeEnd = compoundRect.width();
2158                 dr.mDrawableHeightEnd = compoundRect.height();
2159             } else {
2160                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2161             }
2162             resetResolvedDrawables();
2163             resolveDrawables();
2164             applyCompoundDrawableTint();
2165         }
2166     }
2167
2168     @android.view.RemotableViewMethod
2169     @Override
2170     public void setEnabled(boolean enabled) {
2171         if (enabled == isEnabled()) {
2172             return;
2173         }
2174
2175         if (!enabled) {
2176             // Hide the soft input if the currently active TextView is disabled
2177             InputMethodManager imm = InputMethodManager.peekInstance();
2178             if (imm != null && imm.isActive(this)) {
2179                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2180             }
2181         }
2182
2183         super.setEnabled(enabled);
2184
2185         if (enabled) {
2186             // Make sure IME is updated with current editor info.
2187             InputMethodManager imm = InputMethodManager.peekInstance();
2188             if (imm != null) imm.restartInput(this);
2189         }
2190
2191         // Will change text color
2192         if (mEditor != null) {
2193             mEditor.invalidateTextDisplayList();
2194             mEditor.prepareCursorControllers();
2195
2196             // start or stop the cursor blinking as appropriate
2197             mEditor.makeBlink();
2198         }
2199     }
2200
2201     /**
2202      * Sets the typeface and style in which the text should be displayed,
2203      * and turns on the fake bold and italic bits in the Paint if the
2204      * Typeface that you provided does not have all the bits in the
2205      * style that you specified.
2206      *
2207      * @attr ref android.R.styleable#TextView_typeface
2208      * @attr ref android.R.styleable#TextView_textStyle
2209      */
2210     public void setTypeface(Typeface tf, int style) {
2211         if (style > 0) {
2212             if (tf == null) {
2213                 tf = Typeface.defaultFromStyle(style);
2214             } else {
2215                 tf = Typeface.create(tf, style);
2216             }
2217
2218             setTypeface(tf);
2219             // now compute what (if any) algorithmic styling is needed
2220             int typefaceStyle = tf != null ? tf.getStyle() : 0;
2221             int need = style & ~typefaceStyle;
2222             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2223             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2224         } else {
2225             mTextPaint.setFakeBoldText(false);
2226             mTextPaint.setTextSkewX(0);
2227             setTypeface(tf);
2228         }
2229     }
2230
2231     /**
2232      * Subclasses override this to specify that they have a KeyListener
2233      * by default even if not specifically called for in the XML options.
2234      */
2235     protected boolean getDefaultEditable() {
2236         return false;
2237     }
2238
2239     /**
2240      * Subclasses override this to specify a default movement method.
2241      */
2242     protected MovementMethod getDefaultMovementMethod() {
2243         return null;
2244     }
2245
2246     /**
2247      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2248      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2249      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2250      * the return value from this method to Spannable or Editable, respectively.
2251      *
2252      * <p>The content of the return value should not be modified. If you want a modifiable one, you
2253      * should make your own copy first.</p>
2254      *
2255      * @return The text displayed by the text view.
2256      * @attr ref android.R.styleable#TextView_text
2257      */
2258     @ViewDebug.CapturedViewProperty
2259     public CharSequence getText() {
2260         return mText;
2261     }
2262
2263     /**
2264      * Returns the length, in characters, of the text managed by this TextView
2265      * @return The length of the text managed by the TextView in characters.
2266      */
2267     public int length() {
2268         return mText.length();
2269     }
2270
2271     /**
2272      * Return the text that TextView is displaying as an Editable object. If the text is not
2273      * editable, null is returned.
2274      *
2275      * @see #getText
2276      */
2277     public Editable getEditableText() {
2278         return (mText instanceof Editable) ? (Editable) mText : null;
2279     }
2280
2281     /**
2282      * Gets the vertical distance between lines of text, in pixels.
2283      * Note that markup within the text can cause individual lines
2284      * to be taller or shorter than this height, and the layout may
2285      * contain additional first-or last-line padding.
2286      * @return The height of one standard line in pixels.
2287      */
2288     public int getLineHeight() {
2289         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2290     }
2291
2292     /**
2293      * Gets the {@link android.text.Layout} that is currently being used to display the text.
2294      * This value can be null if the text or width has recently changed.
2295      * @return The Layout that is currently being used to display the text.
2296      */
2297     public final Layout getLayout() {
2298         return mLayout;
2299     }
2300
2301     /**
2302      * @return the {@link android.text.Layout} that is currently being used to
2303      * display the hint text. This can be null.
2304      */
2305     final Layout getHintLayout() {
2306         return mHintLayout;
2307     }
2308
2309     /**
2310      * Retrieve the {@link android.content.UndoManager} that is currently associated
2311      * with this TextView.  By default there is no associated UndoManager, so null
2312      * is returned.  One can be associated with the TextView through
2313      * {@link #setUndoManager(android.content.UndoManager, String)}
2314      *
2315      * @hide
2316      */
2317     public final UndoManager getUndoManager() {
2318         // TODO: Consider supporting a global undo manager.
2319         throw new UnsupportedOperationException("not implemented");
2320     }
2321
2322
2323     /**
2324      * @hide
2325      */
2326     @VisibleForTesting
2327     public final Editor getEditorForTesting() {
2328         return mEditor;
2329     }
2330
2331     /**
2332      * Associate an {@link android.content.UndoManager} with this TextView.  Once
2333      * done, all edit operations on the TextView will result in appropriate
2334      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2335      * stack.
2336      *
2337      * @param undoManager The {@link android.content.UndoManager} to associate with
2338      * this TextView, or null to clear any existing association.
2339      * @param tag String tag identifying this particular TextView owner in the
2340      * UndoManager.  This is used to keep the correct association with the
2341      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2342      *
2343      * @hide
2344      */
2345     public final void setUndoManager(UndoManager undoManager, String tag) {
2346         // TODO: Consider supporting a global undo manager. An implementation will need to:
2347         // * createEditorIfNeeded()
2348         // * Promote to BufferType.EDITABLE if needed.
2349         // * Update the UndoManager and UndoOwner.
2350         // Likewise it will need to be able to restore the default UndoManager.
2351         throw new UnsupportedOperationException("not implemented");
2352     }
2353
2354     /**
2355      * Gets the current {@link KeyListener} for the TextView.
2356      * This will frequently be null for non-EditText TextViews.
2357      * @return the current key listener for this TextView.
2358      *
2359      * @attr ref android.R.styleable#TextView_numeric
2360      * @attr ref android.R.styleable#TextView_digits
2361      * @attr ref android.R.styleable#TextView_phoneNumber
2362      * @attr ref android.R.styleable#TextView_inputMethod
2363      * @attr ref android.R.styleable#TextView_capitalize
2364      * @attr ref android.R.styleable#TextView_autoText
2365      */
2366     public final KeyListener getKeyListener() {
2367         return mEditor == null ? null : mEditor.mKeyListener;
2368     }
2369
2370     /**
2371      * Sets the key listener to be used with this TextView.  This can be null
2372      * to disallow user input.  Note that this method has significant and
2373      * subtle interactions with soft keyboards and other input method:
2374      * see {@link KeyListener#getInputType() KeyListener.getContentType()}
2375      * for important details.  Calling this method will replace the current
2376      * content type of the text view with the content type returned by the
2377      * key listener.
2378      * <p>
2379      * Be warned that if you want a TextView with a key listener or movement
2380      * method not to be focusable, or if you want a TextView without a
2381      * key listener or movement method to be focusable, you must call
2382      * {@link #setFocusable} again after calling this to get the focusability
2383      * back the way you want it.
2384      *
2385      * @attr ref android.R.styleable#TextView_numeric
2386      * @attr ref android.R.styleable#TextView_digits
2387      * @attr ref android.R.styleable#TextView_phoneNumber
2388      * @attr ref android.R.styleable#TextView_inputMethod
2389      * @attr ref android.R.styleable#TextView_capitalize
2390      * @attr ref android.R.styleable#TextView_autoText
2391      */
2392     public void setKeyListener(KeyListener input) {
2393         mListenerChanged = true;
2394         setKeyListenerOnly(input);
2395         fixFocusableAndClickableSettings();
2396
2397         if (input != null) {
2398             createEditorIfNeeded();
2399             setInputTypeFromEditor();
2400         } else {
2401             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2402         }
2403
2404         InputMethodManager imm = InputMethodManager.peekInstance();
2405         if (imm != null) imm.restartInput(this);
2406     }
2407
2408     private void setInputTypeFromEditor() {
2409         try {
2410             mEditor.mInputType = mEditor.mKeyListener.getInputType();
2411         } catch (IncompatibleClassChangeError e) {
2412             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2413         }
2414         // Change inputType, without affecting transformation.
2415         // No need to applySingleLine since mSingleLine is unchanged.
2416         setInputTypeSingleLine(mSingleLine);
2417     }
2418
2419     private void setKeyListenerOnly(KeyListener input) {
2420         if (mEditor == null && input == null) return; // null is the default value
2421
2422         createEditorIfNeeded();
2423         if (mEditor.mKeyListener != input) {
2424             mEditor.mKeyListener = input;
2425             if (input != null && !(mText instanceof Editable)) {
2426                 setText(mText);
2427             }
2428
2429             setFilters((Editable) mText, mFilters);
2430         }
2431     }
2432
2433     /**
2434      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2435      * which provides positioning, scrolling, and text selection functionality.
2436      * This will frequently be null for non-EditText TextViews.
2437      * @return the movement method being used for this TextView.
2438      * @see android.text.method.MovementMethod
2439      */
2440     public final MovementMethod getMovementMethod() {
2441         return mMovement;
2442     }
2443
2444     /**
2445      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2446      * for this TextView. This can be null to disallow using the arrow keys to move the
2447      * cursor or scroll the view.
2448      * <p>
2449      * Be warned that if you want a TextView with a key listener or movement
2450      * method not to be focusable, or if you want a TextView without a
2451      * key listener or movement method to be focusable, you must call
2452      * {@link #setFocusable} again after calling this to get the focusability
2453      * back the way you want it.
2454      */
2455     public final void setMovementMethod(MovementMethod movement) {
2456         if (mMovement != movement) {
2457             mMovement = movement;
2458
2459             if (movement != null && !(mText instanceof Spannable)) {
2460                 setText(mText);
2461             }
2462
2463             fixFocusableAndClickableSettings();
2464
2465             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2466             // mMovement
2467             if (mEditor != null) mEditor.prepareCursorControllers();
2468         }
2469     }
2470
2471     private void fixFocusableAndClickableSettings() {
2472         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2473             setFocusable(FOCUSABLE);
2474             setClickable(true);
2475             setLongClickable(true);
2476         } else {
2477             setFocusable(FOCUSABLE_AUTO);
2478             setClickable(false);
2479             setLongClickable(false);
2480         }
2481     }
2482
2483     /**
2484      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2485      * This is frequently null, except for single-line and password fields.
2486      * @return the current transformation method for this TextView.
2487      *
2488      * @attr ref android.R.styleable#TextView_password
2489      * @attr ref android.R.styleable#TextView_singleLine
2490      */
2491     public final TransformationMethod getTransformationMethod() {
2492         return mTransformation;
2493     }
2494
2495     /**
2496      * Sets the transformation that is applied to the text that this
2497      * TextView is displaying.
2498      *
2499      * @attr ref android.R.styleable#TextView_password
2500      * @attr ref android.R.styleable#TextView_singleLine
2501      */
2502     public final void setTransformationMethod(TransformationMethod method) {
2503         if (method == mTransformation) {
2504             // Avoid the setText() below if the transformation is
2505             // the same.
2506             return;
2507         }
2508         if (mTransformation != null) {
2509             if (mText instanceof Spannable) {
2510                 ((Spannable) mText).removeSpan(mTransformation);
2511             }
2512         }
2513
2514         mTransformation = method;
2515
2516         if (method instanceof TransformationMethod2) {
2517             TransformationMethod2 method2 = (TransformationMethod2) method;
2518             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2519             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2520         } else {
2521             mAllowTransformationLengthChange = false;
2522         }
2523
2524         setText(mText);
2525
2526         if (hasPasswordTransformationMethod()) {
2527             notifyViewAccessibilityStateChangedIfNeeded(
2528                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2529         }
2530
2531         // PasswordTransformationMethod always have LTR text direction heuristics returned by
2532         // getTextDirectionHeuristic, needs reset
2533         mTextDir = getTextDirectionHeuristic();
2534     }
2535
2536     /**
2537      * Returns the top padding of the view, plus space for the top
2538      * Drawable if any.
2539      */
2540     public int getCompoundPaddingTop() {
2541         final Drawables dr = mDrawables;
2542         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2543             return mPaddingTop;
2544         } else {
2545             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2546         }
2547     }
2548
2549     /**
2550      * Returns the bottom padding of the view, plus space for the bottom
2551      * Drawable if any.
2552      */
2553     public int getCompoundPaddingBottom() {
2554         final Drawables dr = mDrawables;
2555         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2556             return mPaddingBottom;
2557         } else {
2558             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2559         }
2560     }
2561
2562     /**
2563      * Returns the left padding of the view, plus space for the left
2564      * Drawable if any.
2565      */
2566     public int getCompoundPaddingLeft() {
2567         final Drawables dr = mDrawables;
2568         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2569             return mPaddingLeft;
2570         } else {
2571             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2572         }
2573     }
2574
2575     /**
2576      * Returns the right padding of the view, plus space for the right
2577      * Drawable if any.
2578      */
2579     public int getCompoundPaddingRight() {
2580         final Drawables dr = mDrawables;
2581         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2582             return mPaddingRight;
2583         } else {
2584             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2585         }
2586     }
2587
2588     /**
2589      * Returns the start padding of the view, plus space for the start
2590      * Drawable if any.
2591      */
2592     public int getCompoundPaddingStart() {
2593         resolveDrawables();
2594         switch(getLayoutDirection()) {
2595             default:
2596             case LAYOUT_DIRECTION_LTR:
2597                 return getCompoundPaddingLeft();
2598             case LAYOUT_DIRECTION_RTL:
2599                 return getCompoundPaddingRight();
2600         }
2601     }
2602
2603     /**
2604      * Returns the end padding of the view, plus space for the end
2605      * Drawable if any.
2606      */
2607     public int getCompoundPaddingEnd() {
2608         resolveDrawables();
2609         switch(getLayoutDirection()) {
2610             default:
2611             case LAYOUT_DIRECTION_LTR:
2612                 return getCompoundPaddingRight();
2613             case LAYOUT_DIRECTION_RTL:
2614                 return getCompoundPaddingLeft();
2615         }
2616     }
2617
2618     /**
2619      * Returns the extended top padding of the view, including both the
2620      * top Drawable if any and any extra space to keep more than maxLines
2621      * of text from showing.  It is only valid to call this after measuring.
2622      */
2623     public int getExtendedPaddingTop() {
2624         if (mMaxMode != LINES) {
2625             return getCompoundPaddingTop();
2626         }
2627
2628         if (mLayout == null) {
2629             assumeLayout();
2630         }
2631
2632         if (mLayout.getLineCount() <= mMaximum) {
2633             return getCompoundPaddingTop();
2634         }
2635
2636         int top = getCompoundPaddingTop();
2637         int bottom = getCompoundPaddingBottom();
2638         int viewht = getHeight() - top - bottom;
2639         int layoutht = mLayout.getLineTop(mMaximum);
2640
2641         if (layoutht >= viewht) {
2642             return top;
2643         }
2644
2645         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2646         if (gravity == Gravity.TOP) {
2647             return top;
2648         } else if (gravity == Gravity.BOTTOM) {
2649             return top + viewht - layoutht;
2650         } else { // (gravity == Gravity.CENTER_VERTICAL)
2651             return top + (viewht - layoutht) / 2;
2652         }
2653     }
2654
2655     /**
2656      * Returns the extended bottom padding of the view, including both the
2657      * bottom Drawable if any and any extra space to keep more than maxLines
2658      * of text from showing.  It is only valid to call this after measuring.
2659      */
2660     public int getExtendedPaddingBottom() {
2661         if (mMaxMode != LINES) {
2662             return getCompoundPaddingBottom();
2663         }
2664
2665         if (mLayout == null) {
2666             assumeLayout();
2667         }
2668
2669         if (mLayout.getLineCount() <= mMaximum) {
2670             return getCompoundPaddingBottom();
2671         }
2672
2673         int top = getCompoundPaddingTop();
2674         int bottom = getCompoundPaddingBottom();
2675         int viewht = getHeight() - top - bottom;
2676         int layoutht = mLayout.getLineTop(mMaximum);
2677
2678         if (layoutht >= viewht) {
2679             return bottom;
2680         }
2681
2682         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2683         if (gravity == Gravity.TOP) {
2684             return bottom + viewht - layoutht;
2685         } else if (gravity == Gravity.BOTTOM) {
2686             return bottom;
2687         } else { // (gravity == Gravity.CENTER_VERTICAL)
2688             return bottom + (viewht - layoutht) / 2;
2689         }
2690     }
2691
2692     /**
2693      * Returns the total left padding of the view, including the left
2694      * Drawable if any.
2695      */
2696     public int getTotalPaddingLeft() {
2697         return getCompoundPaddingLeft();
2698     }
2699
2700     /**
2701      * Returns the total right padding of the view, including the right
2702      * Drawable if any.
2703      */
2704     public int getTotalPaddingRight() {
2705         return getCompoundPaddingRight();
2706     }
2707
2708     /**
2709      * Returns the total start padding of the view, including the start
2710      * Drawable if any.
2711      */
2712     public int getTotalPaddingStart() {
2713         return getCompoundPaddingStart();
2714     }
2715
2716     /**
2717      * Returns the total end padding of the view, including the end
2718      * Drawable if any.
2719      */
2720     public int getTotalPaddingEnd() {
2721         return getCompoundPaddingEnd();
2722     }
2723
2724     /**
2725      * Returns the total top padding of the view, including the top
2726      * Drawable if any, the extra space to keep more than maxLines
2727      * from showing, and the vertical offset for gravity, if any.
2728      */
2729     public int getTotalPaddingTop() {
2730         return getExtendedPaddingTop() + getVerticalOffset(true);
2731     }
2732
2733     /**
2734      * Returns the total bottom padding of the view, including the bottom
2735      * Drawable if any, the extra space to keep more than maxLines
2736      * from showing, and the vertical offset for gravity, if any.
2737      */
2738     public int getTotalPaddingBottom() {
2739         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2740     }
2741
2742     /**
2743      * Sets the Drawables (if any) to appear to the left of, above, to the
2744      * right of, and below the text. Use {@code null} if you do not want a
2745      * Drawable there. The Drawables must already have had
2746      * {@link Drawable#setBounds} called.
2747      * <p>
2748      * Calling this method will overwrite any Drawables previously set using
2749      * {@link #setCompoundDrawablesRelative} or related methods.
2750      *
2751      * @attr ref android.R.styleable#TextView_drawableLeft
2752      * @attr ref android.R.styleable#TextView_drawableTop
2753      * @attr ref android.R.styleable#TextView_drawableRight
2754      * @attr ref android.R.styleable#TextView_drawableBottom
2755      */
2756     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2757             @Nullable Drawable right, @Nullable Drawable bottom) {
2758         Drawables dr = mDrawables;
2759
2760         // We're switching to absolute, discard relative.
2761         if (dr != null) {
2762             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2763             dr.mDrawableStart = null;
2764             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2765             dr.mDrawableEnd = null;
2766             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2767             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2768         }
2769
2770         final boolean drawables = left != null || top != null || right != null || bottom != null;
2771         if (!drawables) {
2772             // Clearing drawables...  can we free the data structure?
2773             if (dr != null) {
2774                 if (!dr.hasMetadata()) {
2775                     mDrawables = null;
2776                 } else {
2777                     // We need to retain the last set padding, so just clear
2778                     // out all of the fields in the existing structure.
2779                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2780                         if (dr.mShowing[i] != null) {
2781                             dr.mShowing[i].setCallback(null);
2782                         }
2783                         dr.mShowing[i] = null;
2784                     }
2785                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2786                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2787                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2788                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2789                 }
2790             }
2791         } else {
2792             if (dr == null) {
2793                 mDrawables = dr = new Drawables(getContext());
2794             }
2795
2796             mDrawables.mOverride = false;
2797
2798             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2799                 dr.mShowing[Drawables.LEFT].setCallback(null);
2800             }
2801             dr.mShowing[Drawables.LEFT] = left;
2802
2803             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2804                 dr.mShowing[Drawables.TOP].setCallback(null);
2805             }
2806             dr.mShowing[Drawables.TOP] = top;
2807
2808             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2809                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2810             }
2811             dr.mShowing[Drawables.RIGHT] = right;
2812
2813             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2814                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2815             }
2816             dr.mShowing[Drawables.BOTTOM] = bottom;
2817
2818             final Rect compoundRect = dr.mCompoundRect;
2819             int[] state;
2820
2821             state = getDrawableState();
2822
2823             if (left != null) {
2824                 left.setState(state);
2825                 left.copyBounds(compoundRect);
2826                 left.setCallback(this);
2827                 dr.mDrawableSizeLeft = compoundRect.width();
2828                 dr.mDrawableHeightLeft = compoundRect.height();
2829             } else {
2830                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2831             }
2832
2833             if (right != null) {
2834                 right.setState(state);
2835                 right.copyBounds(compoundRect);
2836                 right.setCallback(this);
2837                 dr.mDrawableSizeRight = compoundRect.width();
2838                 dr.mDrawableHeightRight = compoundRect.height();
2839             } else {
2840                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2841             }
2842
2843             if (top != null) {
2844                 top.setState(state);
2845                 top.copyBounds(compoundRect);
2846                 top.setCallback(this);
2847                 dr.mDrawableSizeTop = compoundRect.height();
2848                 dr.mDrawableWidthTop = compoundRect.width();
2849             } else {
2850                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2851             }
2852
2853             if (bottom != null) {
2854                 bottom.setState(state);
2855                 bottom.copyBounds(compoundRect);
2856                 bottom.setCallback(this);
2857                 dr.mDrawableSizeBottom = compoundRect.height();
2858                 dr.mDrawableWidthBottom = compoundRect.width();
2859             } else {
2860                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2861             }
2862         }
2863
2864         // Save initial left/right drawables
2865         if (dr != null) {
2866             dr.mDrawableLeftInitial = left;
2867             dr.mDrawableRightInitial = right;
2868         }
2869
2870         resetResolvedDrawables();
2871         resolveDrawables();
2872         applyCompoundDrawableTint();
2873         invalidate();
2874         requestLayout();
2875     }
2876
2877     /**
2878      * Sets the Drawables (if any) to appear to the left of, above, to the
2879      * right of, and below the text. Use 0 if you do not want a Drawable there.
2880      * The Drawables' bounds will be set to their intrinsic bounds.
2881      * <p>
2882      * Calling this method will overwrite any Drawables previously set using
2883      * {@link #setCompoundDrawablesRelative} or related methods.
2884      *
2885      * @param left Resource identifier of the left Drawable.
2886      * @param top Resource identifier of the top Drawable.
2887      * @param right Resource identifier of the right Drawable.
2888      * @param bottom Resource identifier of the bottom Drawable.
2889      *
2890      * @attr ref android.R.styleable#TextView_drawableLeft
2891      * @attr ref android.R.styleable#TextView_drawableTop
2892      * @attr ref android.R.styleable#TextView_drawableRight
2893      * @attr ref android.R.styleable#TextView_drawableBottom
2894      */
2895     @android.view.RemotableViewMethod
2896     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2897             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2898         final Context context = getContext();
2899         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2900                 top != 0 ? context.getDrawable(top) : null,
2901                 right != 0 ? context.getDrawable(right) : null,
2902                 bottom != 0 ? context.getDrawable(bottom) : null);
2903     }
2904
2905     /**
2906      * Sets the Drawables (if any) to appear to the left of, above, to the
2907      * right of, and below the text. Use {@code null} if you do not want a
2908      * Drawable there. The Drawables' bounds will be set to their intrinsic
2909      * bounds.
2910      * <p>
2911      * Calling this method will overwrite any Drawables previously set using
2912      * {@link #setCompoundDrawablesRelative} or related methods.
2913      *
2914      * @attr ref android.R.styleable#TextView_drawableLeft
2915      * @attr ref android.R.styleable#TextView_drawableTop
2916      * @attr ref android.R.styleable#TextView_drawableRight
2917      * @attr ref android.R.styleable#TextView_drawableBottom
2918      */
2919     @android.view.RemotableViewMethod
2920     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2921             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2922
2923         if (left != null) {
2924             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2925         }
2926         if (right != null) {
2927             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2928         }
2929         if (top != null) {
2930             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2931         }
2932         if (bottom != null) {
2933             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2934         }
2935         setCompoundDrawables(left, top, right, bottom);
2936     }
2937
2938     /**
2939      * Sets the Drawables (if any) to appear to the start of, above, to the end
2940      * of, and below the text. Use {@code null} if you do not want a Drawable
2941      * there. The Drawables must already have had {@link Drawable#setBounds}
2942      * called.
2943      * <p>
2944      * Calling this method will overwrite any Drawables previously set using
2945      * {@link #setCompoundDrawables} or related methods.
2946      *
2947      * @attr ref android.R.styleable#TextView_drawableStart
2948      * @attr ref android.R.styleable#TextView_drawableTop
2949      * @attr ref android.R.styleable#TextView_drawableEnd
2950      * @attr ref android.R.styleable#TextView_drawableBottom
2951      */
2952     @android.view.RemotableViewMethod
2953     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2954             @Nullable Drawable end, @Nullable Drawable bottom) {
2955         Drawables dr = mDrawables;
2956
2957         // We're switching to relative, discard absolute.
2958         if (dr != null) {
2959             if (dr.mShowing[Drawables.LEFT] != null) {
2960                 dr.mShowing[Drawables.LEFT].setCallback(null);
2961             }
2962             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2963             if (dr.mShowing[Drawables.RIGHT] != null) {
2964                 dr.mShowing[Drawables.RIGHT].setCallback(null);
2965             }
2966             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2967             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2968             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2969         }
2970
2971         final boolean drawables = start != null || top != null
2972                 || end != null || bottom != null;
2973
2974         if (!drawables) {
2975             // Clearing drawables...  can we free the data structure?
2976             if (dr != null) {
2977                 if (!dr.hasMetadata()) {
2978                     mDrawables = null;
2979                 } else {
2980                     // We need to retain the last set padding, so just clear
2981                     // out all of the fields in the existing structure.
2982                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2983                     dr.mDrawableStart = null;
2984                     if (dr.mShowing[Drawables.TOP] != null) {
2985                         dr.mShowing[Drawables.TOP].setCallback(null);
2986                     }
2987                     dr.mShowing[Drawables.TOP] = null;
2988                     if (dr.mDrawableEnd != null) {
2989                         dr.mDrawableEnd.setCallback(null);
2990                     }
2991                     dr.mDrawableEnd = null;
2992                     if (dr.mShowing[Drawables.BOTTOM] != null) {
2993                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
2994                     }
2995                     dr.mShowing[Drawables.BOTTOM] = null;
2996                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2997                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2998                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2999                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3000                 }
3001             }
3002         } else {
3003             if (dr == null) {
3004                 mDrawables = dr = new Drawables(getContext());
3005             }
3006
3007             mDrawables.mOverride = true;
3008
3009             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3010                 dr.mDrawableStart.setCallback(null);
3011             }
3012             dr.mDrawableStart = start;
3013
3014             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3015                 dr.mShowing[Drawables.TOP].setCallback(null);
3016             }
3017             dr.mShowing[Drawables.TOP] = top;
3018
3019             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3020                 dr.mDrawableEnd.setCallback(null);
3021             }
3022             dr.mDrawableEnd = end;
3023
3024             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3025                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3026             }
3027             dr.mShowing[Drawables.BOTTOM] = bottom;
3028
3029             final Rect compoundRect = dr.mCompoundRect;
3030             int[] state;
3031
3032             state = getDrawableState();
3033
3034             if (start != null) {
3035                 start.setState(state);
3036                 start.copyBounds(compoundRect);
3037                 start.setCallback(this);
3038                 dr.mDrawableSizeStart = compoundRect.width();
3039                 dr.mDrawableHeightStart = compoundRect.height();
3040             } else {
3041                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3042             }
3043
3044             if (end != null) {
3045                 end.setState(state);
3046                 end.copyBounds(compoundRect);
3047                 end.setCallback(this);
3048                 dr.mDrawableSizeEnd = compoundRect.width();
3049                 dr.mDrawableHeightEnd = compoundRect.height();
3050             } else {
3051                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3052             }
3053
3054             if (top != null) {
3055                 top.setState(state);
3056                 top.copyBounds(compoundRect);
3057                 top.setCallback(this);
3058                 dr.mDrawableSizeTop = compoundRect.height();
3059                 dr.mDrawableWidthTop = compoundRect.width();
3060             } else {
3061                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3062             }
3063
3064             if (bottom != null) {
3065                 bottom.setState(state);
3066                 bottom.copyBounds(compoundRect);
3067                 bottom.setCallback(this);
3068                 dr.mDrawableSizeBottom = compoundRect.height();
3069                 dr.mDrawableWidthBottom = compoundRect.width();
3070             } else {
3071                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3072             }
3073         }
3074
3075         resetResolvedDrawables();
3076         resolveDrawables();
3077         invalidate();
3078         requestLayout();
3079     }
3080
3081     /**
3082      * Sets the Drawables (if any) to appear to the start of, above, to the end
3083      * of, and below the text. Use 0 if you do not want a Drawable there. The
3084      * Drawables' bounds will be set to their intrinsic bounds.
3085      * <p>
3086      * Calling this method will overwrite any Drawables previously set using
3087      * {@link #setCompoundDrawables} or related methods.
3088      *
3089      * @param start Resource identifier of the start Drawable.
3090      * @param top Resource identifier of the top Drawable.
3091      * @param end Resource identifier of the end Drawable.
3092      * @param bottom Resource identifier of the bottom Drawable.
3093      *
3094      * @attr ref android.R.styleable#TextView_drawableStart
3095      * @attr ref android.R.styleable#TextView_drawableTop
3096      * @attr ref android.R.styleable#TextView_drawableEnd
3097      * @attr ref android.R.styleable#TextView_drawableBottom
3098      */
3099     @android.view.RemotableViewMethod
3100     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3101             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3102         final Context context = getContext();
3103         setCompoundDrawablesRelativeWithIntrinsicBounds(
3104                 start != 0 ? context.getDrawable(start) : null,
3105                 top != 0 ? context.getDrawable(top) : null,
3106                 end != 0 ? context.getDrawable(end) : null,
3107                 bottom != 0 ? context.getDrawable(bottom) : null);
3108     }
3109
3110     /**
3111      * Sets the Drawables (if any) to appear to the start of, above, to the end
3112      * of, and below the text. Use {@code null} if you do not want a Drawable
3113      * there. The Drawables' bounds will be set to their intrinsic bounds.
3114      * <p>
3115      * Calling this method will overwrite any Drawables previously set using
3116      * {@link #setCompoundDrawables} or related methods.
3117      *
3118      * @attr ref android.R.styleable#TextView_drawableStart
3119      * @attr ref android.R.styleable#TextView_drawableTop
3120      * @attr ref android.R.styleable#TextView_drawableEnd
3121      * @attr ref android.R.styleable#TextView_drawableBottom
3122      */
3123     @android.view.RemotableViewMethod
3124     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3125             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3126
3127         if (start != null) {
3128             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3129         }
3130         if (end != null) {
3131             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3132         }
3133         if (top != null) {
3134             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3135         }
3136         if (bottom != null) {
3137             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3138         }
3139         setCompoundDrawablesRelative(start, top, end, bottom);
3140     }
3141
3142     /**
3143      * Returns drawables for the left, top, right, and bottom borders.
3144      *
3145      * @attr ref android.R.styleable#TextView_drawableLeft
3146      * @attr ref android.R.styleable#TextView_drawableTop
3147      * @attr ref android.R.styleable#TextView_drawableRight
3148      * @attr ref android.R.styleable#TextView_drawableBottom
3149      */
3150     @NonNull
3151     public Drawable[] getCompoundDrawables() {
3152         final Drawables dr = mDrawables;
3153         if (dr != null) {
3154             return dr.mShowing.clone();
3155         } else {
3156             return new Drawable[] { null, null, null, null };
3157         }
3158     }
3159
3160     /**
3161      * Returns drawables for the start, top, end, and bottom borders.
3162      *
3163      * @attr ref android.R.styleable#TextView_drawableStart
3164      * @attr ref android.R.styleable#TextView_drawableTop
3165      * @attr ref android.R.styleable#TextView_drawableEnd
3166      * @attr ref android.R.styleable#TextView_drawableBottom
3167      */
3168     @NonNull
3169     public Drawable[] getCompoundDrawablesRelative() {
3170         final Drawables dr = mDrawables;
3171         if (dr != null) {
3172             return new Drawable[] {
3173                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3174                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3175             };
3176         } else {
3177             return new Drawable[] { null, null, null, null };
3178         }
3179     }
3180
3181     /**
3182      * Sets the size of the padding between the compound drawables and
3183      * the text.
3184      *
3185      * @attr ref android.R.styleable#TextView_drawablePadding
3186      */
3187     @android.view.RemotableViewMethod
3188     public void setCompoundDrawablePadding(int pad) {
3189         Drawables dr = mDrawables;
3190         if (pad == 0) {
3191             if (dr != null) {
3192                 dr.mDrawablePadding = pad;
3193             }
3194         } else {
3195             if (dr == null) {
3196                 mDrawables = dr = new Drawables(getContext());
3197             }
3198             dr.mDrawablePadding = pad;
3199         }
3200
3201         invalidate();
3202         requestLayout();
3203     }
3204
3205     /**
3206      * Returns the padding between the compound drawables and the text.
3207      *
3208      * @attr ref android.R.styleable#TextView_drawablePadding
3209      */
3210     public int getCompoundDrawablePadding() {
3211         final Drawables dr = mDrawables;
3212         return dr != null ? dr.mDrawablePadding : 0;
3213     }
3214
3215     /**
3216      * Applies a tint to the compound drawables. Does not modify the
3217      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3218      * <p>
3219      * Subsequent calls to
3220      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3221      * and related methods will automatically mutate the drawables and apply
3222      * the specified tint and tint mode using
3223      * {@link Drawable#setTintList(ColorStateList)}.
3224      *
3225      * @param tint the tint to apply, may be {@code null} to clear tint
3226      *
3227      * @attr ref android.R.styleable#TextView_drawableTint
3228      * @see #getCompoundDrawableTintList()
3229      * @see Drawable#setTintList(ColorStateList)
3230      */
3231     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3232         if (mDrawables == null) {
3233             mDrawables = new Drawables(getContext());
3234         }
3235         mDrawables.mTintList = tint;
3236         mDrawables.mHasTint = true;
3237
3238         applyCompoundDrawableTint();
3239     }
3240
3241     /**
3242      * @return the tint applied to the compound drawables
3243      * @attr ref android.R.styleable#TextView_drawableTint
3244      * @see #setCompoundDrawableTintList(ColorStateList)
3245      */
3246     public ColorStateList getCompoundDrawableTintList() {
3247         return mDrawables != null ? mDrawables.mTintList : null;
3248     }
3249
3250     /**
3251      * Specifies the blending mode used to apply the tint specified by
3252      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3253      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3254      *
3255      * @param tintMode the blending mode used to apply the tint, may be
3256      *                 {@code null} to clear tint
3257      * @attr ref android.R.styleable#TextView_drawableTintMode
3258      * @see #setCompoundDrawableTintList(ColorStateList)
3259      * @see Drawable#setTintMode(PorterDuff.Mode)
3260      */
3261     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3262         if (mDrawables == null) {
3263             mDrawables = new Drawables(getContext());
3264         }
3265         mDrawables.mTintMode = tintMode;
3266         mDrawables.mHasTintMode = true;
3267
3268         applyCompoundDrawableTint();
3269     }
3270
3271     /**
3272      * Returns the blending mode used to apply the tint to the compound
3273      * drawables, if specified.
3274      *
3275      * @return the blending mode used to apply the tint to the compound
3276      *         drawables
3277      * @attr ref android.R.styleable#TextView_drawableTintMode
3278      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3279      */
3280     public PorterDuff.Mode getCompoundDrawableTintMode() {
3281         return mDrawables != null ? mDrawables.mTintMode : null;
3282     }
3283
3284     private void applyCompoundDrawableTint() {
3285         if (mDrawables == null) {
3286             return;
3287         }
3288
3289         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3290             final ColorStateList tintList = mDrawables.mTintList;
3291             final PorterDuff.Mode tintMode = mDrawables.mTintMode;
3292             final boolean hasTint = mDrawables.mHasTint;
3293             final boolean hasTintMode = mDrawables.mHasTintMode;
3294             final int[] state = getDrawableState();
3295
3296             for (Drawable dr : mDrawables.mShowing) {
3297                 if (dr == null) {
3298                     continue;
3299                 }
3300
3301                 if (dr == mDrawables.mDrawableError) {
3302                     // From a developer's perspective, the error drawable isn't
3303                     // a compound drawable. Don't apply the generic compound
3304                     // drawable tint to it.
3305                     continue;
3306                 }
3307
3308                 dr.mutate();
3309
3310                 if (hasTint) {
3311                     dr.setTintList(tintList);
3312                 }
3313
3314                 if (hasTintMode) {
3315                     dr.setTintMode(tintMode);
3316                 }
3317
3318                 // The drawable (or one of its children) may not have been
3319                 // stateful before applying the tint, so let's try again.
3320                 if (dr.isStateful()) {
3321                     dr.setState(state);
3322                 }
3323             }
3324         }
3325     }
3326
3327     @Override
3328     public void setPadding(int left, int top, int right, int bottom) {
3329         if (left != mPaddingLeft
3330                 || right != mPaddingRight
3331                 || top != mPaddingTop
3332                 ||  bottom != mPaddingBottom) {
3333             nullLayouts();
3334         }
3335
3336         // the super call will requestLayout()
3337         super.setPadding(left, top, right, bottom);
3338         invalidate();
3339     }
3340
3341     @Override
3342     public void setPaddingRelative(int start, int top, int end, int bottom) {
3343         if (start != getPaddingStart()
3344                 || end != getPaddingEnd()
3345                 || top != mPaddingTop
3346                 || bottom != mPaddingBottom) {
3347             nullLayouts();
3348         }
3349
3350         // the super call will requestLayout()
3351         super.setPaddingRelative(start, top, end, bottom);
3352         invalidate();
3353     }
3354
3355     /**
3356      * Gets the autolink mask of the text.  See {@link
3357      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3358      * possible values.
3359      *
3360      * @attr ref android.R.styleable#TextView_autoLink
3361      */
3362     public final int getAutoLinkMask() {
3363         return mAutoLinkMask;
3364     }
3365
3366     /**
3367      * Sets the text appearance from the specified style resource.
3368      * <p>
3369      * Use a framework-defined {@code TextAppearance} style like
3370      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3371      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3372      * set of attributes that can be used in a custom style.
3373      *
3374      * @param resId the resource identifier of the style to apply
3375      * @attr ref android.R.styleable#TextView_textAppearance
3376      */
3377     @SuppressWarnings("deprecation")
3378     public void setTextAppearance(@StyleRes int resId) {
3379         setTextAppearance(mContext, resId);
3380     }
3381
3382     /**
3383      * Sets the text color, size, style, hint color, and highlight color
3384      * from the specified TextAppearance resource.
3385      *
3386      * @deprecated Use {@link #setTextAppearance(int)} instead.
3387      */
3388     @Deprecated
3389     public void setTextAppearance(Context context, @StyleRes int resId) {
3390         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3391
3392         final int textColorHighlight = ta.getColor(
3393                 R.styleable.TextAppearance_textColorHighlight, 0);
3394         if (textColorHighlight != 0) {
3395             setHighlightColor(textColorHighlight);
3396         }
3397
3398         final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
3399         if (textColor != null) {
3400             setTextColor(textColor);
3401         }
3402
3403         final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
3404         if (textSize != 0) {
3405             setRawTextSize(textSize, true /* shouldRequestLayout */);
3406         }
3407
3408         final ColorStateList textColorHint = ta.getColorStateList(
3409                 R.styleable.TextAppearance_textColorHint);
3410         if (textColorHint != null) {
3411             setHintTextColor(textColorHint);
3412         }
3413
3414         final ColorStateList textColorLink = ta.getColorStateList(
3415                 R.styleable.TextAppearance_textColorLink);
3416         if (textColorLink != null) {
3417             setLinkTextColor(textColorLink);
3418         }
3419
3420         Typeface fontTypeface = null;
3421         String fontFamily = null;
3422         if (!context.isRestricted() && context.canLoadUnsafeResources()) {
3423             try {
3424                 fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
3425             } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3426                 // Expected if it is not a font resource.
3427             }
3428         }
3429         if (fontTypeface == null) {
3430             fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
3431         }
3432         final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
3433         final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
3434         setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
3435
3436         final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
3437         if (shadowColor != 0) {
3438             final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
3439             final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
3440             final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
3441             setShadowLayer(r, dx, dy, shadowColor);
3442         }
3443
3444         if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
3445             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
3446         }
3447
3448         if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
3449             setElegantTextHeight(ta.getBoolean(
3450                     R.styleable.TextAppearance_elegantTextHeight, false));
3451         }
3452
3453         if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
3454             setLetterSpacing(ta.getFloat(
3455                     R.styleable.TextAppearance_letterSpacing, 0));
3456         }
3457
3458         if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
3459             setFontFeatureSettings(ta.getString(
3460                     R.styleable.TextAppearance_fontFeatureSettings));
3461         }
3462
3463         ta.recycle();
3464     }
3465
3466     /**
3467      * Get the default primary {@link Locale} of the text in this TextView. This will always be
3468      * the first member of {@link #getTextLocales()}.
3469      * @return the default primary {@link Locale} of the text in this TextView.
3470      */
3471     @NonNull
3472     public Locale getTextLocale() {
3473         return mTextPaint.getTextLocale();
3474     }
3475
3476     /**
3477      * Get the default {@link LocaleList} of the text in this TextView.
3478      * @return the default {@link LocaleList} of the text in this TextView.
3479      */
3480     @NonNull @Size(min = 1)
3481     public LocaleList getTextLocales() {
3482         return mTextPaint.getTextLocales();
3483     }
3484
3485     private void changeListenerLocaleTo(@Nullable Locale locale) {
3486         if (mListenerChanged) {
3487             // If a listener has been explicitly set, don't change it. We may break something.
3488             return;
3489         }
3490         // The following null check is not absolutely necessary since all calling points of
3491         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
3492         // here in case others would want to call this method in the future.
3493         if (mEditor != null) {
3494             KeyListener listener = mEditor.mKeyListener;
3495             if (listener instanceof DigitsKeyListener) {
3496                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
3497             } else if (listener instanceof DateKeyListener) {
3498                 listener = DateKeyListener.getInstance(locale);
3499             } else if (listener instanceof TimeKeyListener) {
3500                 listener = TimeKeyListener.getInstance(locale);
3501             } else if (listener instanceof DateTimeKeyListener) {
3502                 listener = DateTimeKeyListener.getInstance(locale);
3503             } else {
3504                 return;
3505             }
3506             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
3507             setKeyListenerOnly(listener);
3508             setInputTypeFromEditor();
3509             if (wasPasswordType) {
3510                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
3511                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
3512                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
3513                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
3514                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
3515                 }
3516             }
3517         }
3518     }
3519
3520     /**
3521      * Set the default {@link Locale} of the text in this TextView to a one-member
3522      * {@link LocaleList} containing just the given Locale.
3523      *
3524      * @param locale the {@link Locale} for drawing text, must not be null.
3525      *
3526      * @see #setTextLocales
3527      */
3528     public void setTextLocale(@NonNull Locale locale) {
3529         mLocalesChanged = true;
3530         mTextPaint.setTextLocale(locale);
3531         if (mLayout != null) {
3532             nullLayouts();
3533             requestLayout();
3534             invalidate();
3535         }
3536     }
3537
3538     /**
3539      * Set the default {@link LocaleList} of the text in this TextView to the given value.
3540      *
3541      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
3542      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
3543      * other aspects of text display, including line breaking.
3544      *
3545      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
3546      *
3547      * @see Paint#setTextLocales
3548      */
3549     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
3550         mLocalesChanged = true;
3551         mTextPaint.setTextLocales(locales);
3552         if (mLayout != null) {
3553             nullLayouts();
3554             requestLayout();
3555             invalidate();
3556         }
3557     }
3558
3559     @Override
3560     protected void onConfigurationChanged(Configuration newConfig) {
3561         super.onConfigurationChanged(newConfig);
3562         if (!mLocalesChanged) {
3563             mTextPaint.setTextLocales(LocaleList.getDefault());
3564             if (mLayout != null) {
3565                 nullLayouts();
3566                 requestLayout();
3567                 invalidate();
3568             }
3569         }
3570     }
3571
3572     /**
3573      * @return the size (in pixels) of the default text size in this TextView.
3574      */
3575     @ViewDebug.ExportedProperty(category = "text")
3576     public float getTextSize() {
3577         return mTextPaint.getTextSize();
3578     }
3579
3580     /**
3581      * @return the size (in scaled pixels) of the default text size in this TextView.
3582      * @hide
3583      */
3584     @ViewDebug.ExportedProperty(category = "text")
3585     public float getScaledTextSize() {
3586         return mTextPaint.getTextSize() / mTextPaint.density;
3587     }
3588
3589     /** @hide */
3590     @ViewDebug.ExportedProperty(category = "text", mapping = {
3591             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
3592             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
3593             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
3594             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
3595     })
3596     public int getTypefaceStyle() {
3597         Typeface typeface = mTextPaint.getTypeface();
3598         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
3599     }
3600
3601     /**
3602      * Set the default text size to the given value, interpreted as "scaled
3603      * pixel" units.  This size is adjusted based on the current density and
3604      * user font size preference.
3605      *
3606      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3607      *
3608      * @param size The scaled pixel size.
3609      *
3610      * @attr ref android.R.styleable#TextView_textSize
3611      */
3612     @android.view.RemotableViewMethod
3613     public void setTextSize(float size) {
3614         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
3615     }
3616
3617     /**
3618      * Set the default text size to a given unit and value. See {@link
3619      * TypedValue} for the possible dimension units.
3620      *
3621      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3622      *
3623      * @param unit The desired dimension unit.
3624      * @param size The desired size in the given units.
3625      *
3626      * @attr ref android.R.styleable#TextView_textSize
3627      */
3628     public void setTextSize(int unit, float size) {
3629         if (!isAutoSizeEnabled()) {
3630             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
3631         }
3632     }
3633
3634     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
3635         Context c = getContext();
3636         Resources r;
3637
3638         if (c == null) {
3639             r = Resources.getSystem();
3640         } else {
3641             r = c.getResources();
3642         }
3643
3644         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
3645                 shouldRequestLayout);
3646     }
3647
3648     private void setRawTextSize(float size, boolean shouldRequestLayout) {
3649         if (size != mTextPaint.getTextSize()) {
3650             mTextPaint.setTextSize(size);
3651
3652             if (shouldRequestLayout && mLayout != null) {
3653                 // Do not auto-size right after setting the text size.
3654                 mNeedsAutoSizeText = false;
3655                 nullLayouts();
3656                 requestLayout();
3657                 invalidate();
3658             }
3659         }
3660     }
3661
3662     /**
3663      * Gets the extent by which text should be stretched horizontally.
3664      * This will usually be 1.0.
3665      * @return The horizontal scale factor.
3666      */
3667     public float getTextScaleX() {
3668         return mTextPaint.getTextScaleX();
3669     }
3670
3671     /**
3672      * Sets the horizontal scale factor for text. The default value
3673      * is 1.0. Values greater than 1.0 stretch the text wider.
3674      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
3675      * @param size The horizontal scale factor.
3676      * @attr ref android.R.styleable#TextView_textScaleX
3677      */
3678     @android.view.RemotableViewMethod
3679     public void setTextScaleX(float size) {
3680         if (size != mTextPaint.getTextScaleX()) {
3681             mUserSetTextScaleX = true;
3682             mTextPaint.setTextScaleX(size);
3683
3684             if (mLayout != null) {
3685                 nullLayouts();
3686                 requestLayout();
3687                 invalidate();
3688             }
3689         }
3690     }
3691
3692     /**
3693      * Sets the typeface and style in which the text should be displayed.
3694      * Note that not all Typeface families actually have bold and italic
3695      * variants, so you may need to use
3696      * {@link #setTypeface(Typeface, int)} to get the appearance
3697      * that you actually want.
3698      *
3699      * @see #getTypeface()
3700      *
3701      * @attr ref android.R.styleable#TextView_fontFamily
3702      * @attr ref android.R.styleable#TextView_typeface
3703      * @attr ref android.R.styleable#TextView_textStyle
3704      */
3705     public void setTypeface(Typeface tf) {
3706         if (mTextPaint.getTypeface() != tf) {
3707             mTextPaint.setTypeface(tf);
3708
3709             if (mLayout != null) {
3710                 nullLayouts();
3711                 requestLayout();
3712                 invalidate();
3713             }
3714         }
3715     }
3716
3717     /**
3718      * Gets the current {@link Typeface} that is used to style the text.
3719      * @return The current Typeface.
3720      *
3721      * @see #setTypeface(Typeface)
3722      *
3723      * @attr ref android.R.styleable#TextView_fontFamily
3724      * @attr ref android.R.styleable#TextView_typeface
3725      * @attr ref android.R.styleable#TextView_textStyle
3726      */
3727     public Typeface getTypeface() {
3728         return mTextPaint.getTypeface();
3729     }
3730
3731     /**
3732      * Set the TextView's elegant height metrics flag. This setting selects font
3733      * variants that have not been compacted to fit Latin-based vertical
3734      * metrics, and also increases top and bottom bounds to provide more space.
3735      *
3736      * @param elegant set the paint's elegant metrics flag.
3737      *
3738      * @attr ref android.R.styleable#TextView_elegantTextHeight
3739      */
3740     public void setElegantTextHeight(boolean elegant) {
3741         if (elegant != mTextPaint.isElegantTextHeight()) {
3742             mTextPaint.setElegantTextHeight(elegant);
3743             if (mLayout != null) {
3744                 nullLayouts();
3745                 requestLayout();
3746                 invalidate();
3747             }
3748         }
3749     }
3750
3751     /**
3752      * Gets the text letter-space value, which determines the spacing between characters.
3753      * The value returned is in ems. Normally, this value is 0.0.
3754      * @return The text letter-space value in ems.
3755      *
3756      * @see #setLetterSpacing(float)
3757      * @see Paint#setLetterSpacing
3758      */
3759     public float getLetterSpacing() {
3760         return mTextPaint.getLetterSpacing();
3761     }
3762
3763     /**
3764      * Sets text letter-spacing in em units.  Typical values
3765      * for slight expansion will be around 0.05.  Negative values tighten text.
3766      *
3767      * @see #getLetterSpacing()
3768      * @see Paint#getLetterSpacing
3769      *
3770      * @param letterSpacing A text letter-space value in ems.
3771      * @attr ref android.R.styleable#TextView_letterSpacing
3772      */
3773     @android.view.RemotableViewMethod
3774     public void setLetterSpacing(float letterSpacing) {
3775         if (letterSpacing != mTextPaint.getLetterSpacing()) {
3776             mTextPaint.setLetterSpacing(letterSpacing);
3777
3778             if (mLayout != null) {
3779                 nullLayouts();
3780                 requestLayout();
3781                 invalidate();
3782             }
3783         }
3784     }
3785
3786     /**
3787      * Returns the font feature settings. The format is the same as the CSS
3788      * font-feature-settings attribute:
3789      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
3790      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
3791      *
3792      * @return the currently set font feature settings.  Default is null.
3793      *
3794      * @see #setFontFeatureSettings(String)
3795      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
3796      */
3797     @Nullable
3798     public String getFontFeatureSettings() {
3799         return mTextPaint.getFontFeatureSettings();
3800     }
3801
3802     /**
3803      * Returns the font variation settings.
3804      *
3805      * @return the currently set font variation settings.  Returns null if no variation is
3806      * specified.
3807      *
3808      * @see #setFontVariationSettings(String)
3809      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
3810      */
3811     @Nullable
3812     public String getFontVariationSettings() {
3813         return mTextPaint.getFontVariationSettings();
3814     }
3815
3816     /**
3817      * Sets the break strategy for breaking paragraphs into lines. The default value for
3818      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3819      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3820      * text "dancing" when being edited.
3821      *
3822      * @attr ref android.R.styleable#TextView_breakStrategy
3823      * @see #getBreakStrategy()
3824      */
3825     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3826         mBreakStrategy = breakStrategy;
3827         if (mLayout != null) {
3828             nullLayouts();
3829             requestLayout();
3830             invalidate();
3831         }
3832     }
3833
3834     /**
3835      * Gets the current strategy for breaking paragraphs into lines.
3836      * @return the current strategy for breaking paragraphs into lines.
3837      *
3838      * @attr ref android.R.styleable#TextView_breakStrategy
3839      * @see #setBreakStrategy(int)
3840      */
3841     @Layout.BreakStrategy
3842     public int getBreakStrategy() {
3843         return mBreakStrategy;
3844     }
3845
3846     /**
3847      * Sets the frequency of automatic hyphenation to use when determining word breaks.
3848      * The default value for both TextView and {@link EditText} is
3849      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3850      * Note that the default hyphenation frequency value is set from the theme.
3851      *
3852      * @param hyphenationFrequency The hyphenation frequency to use.
3853      * @attr ref android.R.styleable#TextView_hyphenationFrequency
3854      * @see #getHyphenationFrequency()
3855      */
3856     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3857         mHyphenationFrequency = hyphenationFrequency;
3858         if (mLayout != null) {
3859             nullLayouts();
3860             requestLayout();
3861             invalidate();
3862         }
3863     }
3864
3865     /**
3866      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
3867      * @return the current frequency of automatic hyphenation to be used when determining word
3868      * breaks.
3869      *
3870      * @attr ref android.R.styleable#TextView_hyphenationFrequency
3871      * @see #setHyphenationFrequency(int)
3872      */
3873     @Layout.HyphenationFrequency
3874     public int getHyphenationFrequency() {
3875         return mHyphenationFrequency;
3876     }
3877
3878     /**
3879      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
3880      * last line is too short for justification, the last line will be displayed with the
3881      * alignment set by {@link android.view.View#setTextAlignment}.
3882      *
3883      * @see #getJustificationMode()
3884      */
3885     @Layout.JustificationMode
3886     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
3887         mJustificationMode = justificationMode;
3888         if (mLayout != null) {
3889             nullLayouts();
3890             requestLayout();
3891             invalidate();
3892         }
3893     }
3894
3895     /**
3896      * @return true if currently paragraph justification mode.
3897      *
3898      * @see #setJustificationMode(int)
3899      */
3900     public @Layout.JustificationMode int getJustificationMode() {
3901         return mJustificationMode;
3902     }
3903
3904     /**
3905      * Sets font feature settings. The format is the same as the CSS
3906      * font-feature-settings attribute:
3907      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
3908      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
3909      *
3910      * @param fontFeatureSettings font feature settings represented as CSS compatible string
3911      *
3912      * @see #getFontFeatureSettings()
3913      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
3914      *
3915      * @attr ref android.R.styleable#TextView_fontFeatureSettings
3916      */
3917     @android.view.RemotableViewMethod
3918     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3919         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3920             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3921
3922             if (mLayout != null) {
3923                 nullLayouts();
3924                 requestLayout();
3925                 invalidate();
3926             }
3927         }
3928     }
3929
3930
3931     /**
3932      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
3933      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
3934      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
3935      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
3936      * are invalid. If a specified axis name is not defined in the font, the settings will be
3937      * ignored.
3938      *
3939      * <p>
3940      * Examples,
3941      * <ul>
3942      * <li>Set font width to 150.
3943      * <pre>
3944      * <code>
3945      *   TextView textView = (TextView) findViewById(R.id.textView);
3946      *   textView.setFontVariationSettings("'wdth' 150");
3947      * </code>
3948      * </pre>
3949      * </li>
3950      *
3951      * <li>Set the font slant to 20 degrees and ask for italic style.
3952      * <pre>
3953      * <code>
3954      *   TextView textView = (TextView) findViewById(R.id.textView);
3955      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
3956      * </code>
3957      * </pre>
3958      * </p>
3959      * </li>
3960      * </ul>
3961      *
3962      * @param fontVariationSettings font variation settings. You can pass null or empty string as
3963      *                              no variation settings.
3964      * @return true if the given settings is effective to at least one font file underlying this
3965      *         TextView. This function also returns true for empty settings string. Otherwise
3966      *         returns false.
3967      *
3968      * @throws IllegalArgumentException If given string is not a valid font variation settings
3969      *                                  format.
3970      *
3971      * @see #getFontVariationSettings()
3972      * @see FontVariationAxis
3973      */
3974     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
3975         final String existingSettings = mTextPaint.getFontVariationSettings();
3976         if (fontVariationSettings == existingSettings
3977                 || (fontVariationSettings != null
3978                         && fontVariationSettings.equals(existingSettings))) {
3979             return true;
3980         }
3981         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
3982
3983         if (effective && mLayout != null) {
3984             nullLayouts();
3985             requestLayout();
3986             invalidate();
3987         }
3988         return effective;
3989     }
3990
3991     /**
3992      * Sets the text color for all the states (normal, selected,
3993      * focused) to be this color.
3994      *
3995      * @param color A color value in the form 0xAARRGGBB.
3996      * Do not pass a resource ID. To get a color value from a resource ID, call
3997      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
3998      *
3999      * @see #setTextColor(ColorStateList)
4000      * @see #getTextColors()
4001      *
4002      * @attr ref android.R.styleable#TextView_textColor
4003      */
4004     @android.view.RemotableViewMethod
4005     public void setTextColor(@ColorInt int color) {
4006         mTextColor = ColorStateList.valueOf(color);
4007         updateTextColors();
4008     }
4009
4010     /**
4011      * Sets the text color.
4012      *
4013      * @see #setTextColor(int)
4014      * @see #getTextColors()
4015      * @see #setHintTextColor(ColorStateList)
4016      * @see #setLinkTextColor(ColorStateList)
4017      *
4018      * @attr ref android.R.styleable#TextView_textColor
4019      */
4020     @android.view.RemotableViewMethod
4021     public void setTextColor(ColorStateList colors) {
4022         if (colors == null) {
4023             throw new NullPointerException();
4024         }
4025
4026         mTextColor = colors;
4027         updateTextColors();
4028     }
4029
4030     /**
4031      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4032      *
4033      * @see #setTextColor(ColorStateList)
4034      * @see #setTextColor(int)
4035      *
4036      * @attr ref android.R.styleable#TextView_textColor
4037      */
4038     public final ColorStateList getTextColors() {
4039         return mTextColor;
4040     }
4041
4042     /**
4043      * Return the current color selected for normal text.
4044      *
4045      * @return Returns the current text color.
4046      */
4047     @ColorInt
4048     public final int getCurrentTextColor() {
4049         return mCurTextColor;
4050     }
4051
4052     /**
4053      * Sets the color used to display the selection highlight.
4054      *
4055      * @attr ref android.R.styleable#TextView_textColorHighlight
4056      */
4057     @android.view.RemotableViewMethod
4058     public void setHighlightColor(@ColorInt int color) {
4059         if (mHighlightColor != color) {
4060             mHighlightColor = color;
4061             invalidate();
4062         }
4063     }
4064
4065     /**
4066      * @return the color used to display the selection highlight
4067      *
4068      * @see #setHighlightColor(int)
4069      *
4070      * @attr ref android.R.styleable#TextView_textColorHighlight
4071      */
4072     @ColorInt
4073     public int getHighlightColor() {
4074         return mHighlightColor;
4075     }
4076
4077     /**
4078      * Sets whether the soft input method will be made visible when this
4079      * TextView gets focused. The default is true.
4080      */
4081     @android.view.RemotableViewMethod
4082     public final void setShowSoftInputOnFocus(boolean show) {
4083         createEditorIfNeeded();
4084         mEditor.mShowSoftInputOnFocus = show;
4085     }
4086
4087     /**
4088      * Returns whether the soft input method will be made visible when this
4089      * TextView gets focused. The default is true.
4090      */
4091     public final boolean getShowSoftInputOnFocus() {
4092         // When there is no Editor, return default true value
4093         return mEditor == null || mEditor.mShowSoftInputOnFocus;
4094     }
4095
4096     /**
4097      * Gives the text a shadow of the specified blur radius and color, the specified
4098      * distance from its drawn position.
4099      * <p>
4100      * The text shadow produced does not interact with the properties on view
4101      * that are responsible for real time shadows,
4102      * {@link View#getElevation() elevation} and
4103      * {@link View#getTranslationZ() translationZ}.
4104      *
4105      * @see Paint#setShadowLayer(float, float, float, int)
4106      *
4107      * @attr ref android.R.styleable#TextView_shadowColor
4108      * @attr ref android.R.styleable#TextView_shadowDx
4109      * @attr ref android.R.styleable#TextView_shadowDy
4110      * @attr ref android.R.styleable#TextView_shadowRadius
4111      */
4112     public void setShadowLayer(float radius, float dx, float dy, int color) {
4113         mTextPaint.setShadowLayer(radius, dx, dy, color);
4114
4115         mShadowRadius = radius;
4116         mShadowDx = dx;
4117         mShadowDy = dy;
4118         mShadowColor = color;
4119
4120         // Will change text clip region
4121         if (mEditor != null) {
4122             mEditor.invalidateTextDisplayList();
4123             mEditor.invalidateHandlesAndActionMode();
4124         }
4125         invalidate();
4126     }
4127
4128     /**
4129      * Gets the radius of the shadow layer.
4130      *
4131      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4132      *
4133      * @see #setShadowLayer(float, float, float, int)
4134      *
4135      * @attr ref android.R.styleable#TextView_shadowRadius
4136      */
4137     public float getShadowRadius() {
4138         return mShadowRadius;
4139     }
4140
4141     /**
4142      * @return the horizontal offset of the shadow layer
4143      *
4144      * @see #setShadowLayer(float, float, float, int)
4145      *
4146      * @attr ref android.R.styleable#TextView_shadowDx
4147      */
4148     public float getShadowDx() {
4149         return mShadowDx;
4150     }
4151
4152     /**
4153      * Gets the vertical offset of the shadow layer.
4154      * @return The vertical offset of the shadow layer.
4155      *
4156      * @see #setShadowLayer(float, float, float, int)
4157      *
4158      * @attr ref android.R.styleable#TextView_shadowDy
4159      */
4160     public float getShadowDy() {
4161         return mShadowDy;
4162     }
4163
4164     /**
4165      * Gets the color of the shadow layer.
4166      * @return the color of the shadow layer
4167      *
4168      * @see #setShadowLayer(float, float, float, int)
4169      *
4170      * @attr ref android.R.styleable#TextView_shadowColor
4171      */
4172     @ColorInt
4173     public int getShadowColor() {
4174         return mShadowColor;
4175     }
4176
4177     /**
4178      * Gets the {@link TextPaint} used for the text.
4179      * Use this only to consult the Paint's properties and not to change them.
4180      * @return The base paint used for the text.
4181      */
4182     public TextPaint getPaint() {
4183         return mTextPaint;
4184     }
4185
4186     /**
4187      * Sets the autolink mask of the text.  See {@link
4188      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4189      * possible values.
4190      *
4191      * @attr ref android.R.styleable#TextView_autoLink
4192      */
4193     @android.view.RemotableViewMethod
4194     public final void setAutoLinkMask(int mask) {
4195         mAutoLinkMask = mask;
4196     }
4197
4198     /**
4199      * Sets whether the movement method will automatically be set to
4200      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4201      * set to nonzero and links are detected in {@link #setText}.
4202      * The default is true.
4203      *
4204      * @attr ref android.R.styleable#TextView_linksClickable
4205      */
4206     @android.view.RemotableViewMethod
4207     public final void setLinksClickable(boolean whether) {
4208         mLinksClickable = whether;
4209     }
4210
4211     /**
4212      * Returns whether the movement method will automatically be set to
4213      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4214      * set to nonzero and links are detected in {@link #setText}.
4215      * The default is true.
4216      *
4217      * @attr ref android.R.styleable#TextView_linksClickable
4218      */
4219     public final boolean getLinksClickable() {
4220         return mLinksClickable;
4221     }
4222
4223     /**
4224      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
4225      * (by {@link Linkify} or otherwise) if any.  You can call
4226      * {@link URLSpan#getURL} on them to find where they link to
4227      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
4228      * to find the region of the text they are attached to.
4229      */
4230     public URLSpan[] getUrls() {
4231         if (mText instanceof Spanned) {
4232             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
4233         } else {
4234             return new URLSpan[0];
4235         }
4236     }
4237
4238     /**
4239      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
4240      * TextView.
4241      *
4242      * @see #setHintTextColor(ColorStateList)
4243      * @see #getHintTextColors()
4244      * @see #setTextColor(int)
4245      *
4246      * @attr ref android.R.styleable#TextView_textColorHint
4247      */
4248     @android.view.RemotableViewMethod
4249     public final void setHintTextColor(@ColorInt int color) {
4250         mHintTextColor = ColorStateList.valueOf(color);
4251         updateTextColors();
4252     }
4253
4254     /**
4255      * Sets the color of the hint text.
4256      *
4257      * @see #getHintTextColors()
4258      * @see #setHintTextColor(int)
4259      * @see #setTextColor(ColorStateList)
4260      * @see #setLinkTextColor(ColorStateList)
4261      *
4262      * @attr ref android.R.styleable#TextView_textColorHint
4263      */
4264     public final void setHintTextColor(ColorStateList colors) {
4265         mHintTextColor = colors;
4266         updateTextColors();
4267     }
4268
4269     /**
4270      * @return the color of the hint text, for the different states of this TextView.
4271      *
4272      * @see #setHintTextColor(ColorStateList)
4273      * @see #setHintTextColor(int)
4274      * @see #setTextColor(ColorStateList)
4275      * @see #setLinkTextColor(ColorStateList)
4276      *
4277      * @attr ref android.R.styleable#TextView_textColorHint
4278      */
4279     public final ColorStateList getHintTextColors() {
4280         return mHintTextColor;
4281     }
4282
4283     /**
4284      * <p>Return the current color selected to paint the hint text.</p>
4285      *
4286      * @return Returns the current hint text color.
4287      */
4288     @ColorInt
4289     public final int getCurrentHintTextColor() {
4290         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
4291     }
4292
4293     /**
4294      * Sets the color of links in the text.
4295      *
4296      * @see #setLinkTextColor(ColorStateList)
4297      * @see #getLinkTextColors()
4298      *
4299      * @attr ref android.R.styleable#TextView_textColorLink
4300      */
4301     @android.view.RemotableViewMethod
4302     public final void setLinkTextColor(@ColorInt int color) {
4303         mLinkTextColor = ColorStateList.valueOf(color);
4304         updateTextColors();
4305     }
4306
4307     /**
4308      * Sets the color of links in the text.
4309      *
4310      * @see #setLinkTextColor(int)
4311      * @see #getLinkTextColors()
4312      * @see #setTextColor(ColorStateList)
4313      * @see #setHintTextColor(ColorStateList)
4314      *
4315      * @attr ref android.R.styleable#TextView_textColorLink
4316      */
4317     public final void setLinkTextColor(ColorStateList colors) {
4318         mLinkTextColor = colors;
4319         updateTextColors();
4320     }
4321
4322     /**
4323      * @return the list of colors used to paint the links in the text, for the different states of
4324      * this TextView
4325      *
4326      * @see #setLinkTextColor(ColorStateList)
4327      * @see #setLinkTextColor(int)
4328      *
4329      * @attr ref android.R.styleable#TextView_textColorLink
4330      */
4331     public final ColorStateList getLinkTextColors() {
4332         return mLinkTextColor;
4333     }
4334
4335     /**
4336      * Sets the horizontal alignment of the text and the
4337      * vertical gravity that will be used when there is extra space
4338      * in the TextView beyond what is required for the text itself.
4339      *
4340      * @see android.view.Gravity
4341      * @attr ref android.R.styleable#TextView_gravity
4342      */
4343     public void setGravity(int gravity) {
4344         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
4345             gravity |= Gravity.START;
4346         }
4347         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
4348             gravity |= Gravity.TOP;
4349         }
4350
4351         boolean newLayout = false;
4352
4353         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
4354                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
4355             newLayout = true;
4356         }
4357
4358         if (gravity != mGravity) {
4359             invalidate();
4360         }
4361
4362         mGravity = gravity;
4363
4364         if (mLayout != null && newLayout) {
4365             // XXX this is heavy-handed because no actual content changes.
4366             int want = mLayout.getWidth();
4367             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
4368
4369             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
4370                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
4371         }
4372     }
4373
4374     /**
4375      * Returns the horizontal and vertical alignment of this TextView.
4376      *
4377      * @see android.view.Gravity
4378      * @attr ref android.R.styleable#TextView_gravity
4379      */
4380     public int getGravity() {
4381         return mGravity;
4382     }
4383
4384     /**
4385      * Gets the flags on the Paint being used to display the text.
4386      * @return The flags on the Paint being used to display the text.
4387      * @see Paint#getFlags
4388      */
4389     public int getPaintFlags() {
4390         return mTextPaint.getFlags();
4391     }
4392
4393     /**
4394      * Sets flags on the Paint being used to display the text and
4395      * reflows the text if they are different from the old flags.
4396      * @see Paint#setFlags
4397      */
4398     @android.view.RemotableViewMethod
4399     public void setPaintFlags(int flags) {
4400         if (mTextPaint.getFlags() != flags) {
4401             mTextPaint.setFlags(flags);
4402
4403             if (mLayout != null) {
4404                 nullLayouts();
4405                 requestLayout();
4406                 invalidate();
4407             }
4408         }
4409     }
4410
4411     /**
4412      * Sets whether the text should be allowed to be wider than the
4413      * View is.  If false, it will be wrapped to the width of the View.
4414      *
4415      * @attr ref android.R.styleable#TextView_scrollHorizontally
4416      */
4417     public void setHorizontallyScrolling(boolean whether) {
4418         if (mHorizontallyScrolling != whether) {
4419             mHorizontallyScrolling = whether;
4420
4421             if (mLayout != null) {
4422                 nullLayouts();
4423                 requestLayout();
4424                 invalidate();
4425             }
4426         }
4427     }
4428
4429     /**
4430      * Returns whether the text is allowed to be wider than the View is.
4431      * If false, the text will be wrapped to the width of the View.
4432      *
4433      * @attr ref android.R.styleable#TextView_scrollHorizontally
4434      * @hide
4435      */
4436     public boolean getHorizontallyScrolling() {
4437         return mHorizontallyScrolling;
4438     }
4439
4440     /**
4441      * Sets the height of the TextView to be at least {@code minLines} tall.
4442      * <p>
4443      * This value is used for height calculation if LayoutParams does not force TextView to have an
4444      * exact height. Setting this value overrides other previous minimum height configurations such
4445      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
4446      * this value to 1.
4447      *
4448      * @param minLines the minimum height of TextView in terms of number of lines
4449      *
4450      * @see #getMinLines()
4451      * @see #setLines(int)
4452      *
4453      * @attr ref android.R.styleable#TextView_minLines
4454      */
4455     @android.view.RemotableViewMethod
4456     public void setMinLines(int minLines) {
4457         mMinimum = minLines;
4458         mMinMode = LINES;
4459
4460         requestLayout();
4461         invalidate();
4462     }
4463
4464     /**
4465      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
4466      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
4467      *
4468      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
4469      *         height is not defined in lines
4470      *
4471      * @see #setMinLines(int)
4472      * @see #setLines(int)
4473      *
4474      * @attr ref android.R.styleable#TextView_minLines
4475      */
4476     public int getMinLines() {
4477         return mMinMode == LINES ? mMinimum : -1;
4478     }
4479
4480     /**
4481      * Sets the height of the TextView to be at least {@code minPixels} tall.
4482      * <p>
4483      * This value is used for height calculation if LayoutParams does not force TextView to have an
4484      * exact height. Setting this value overrides previous minimum height configurations such as
4485      * {@link #setMinLines(int)} or {@link #setLines(int)}.
4486      * <p>
4487      * The value given here is different than {@link #setMinimumHeight(int)}. Between
4488      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
4489      * used to decide the final height.
4490      *
4491      * @param minPixels the minimum height of TextView in terms of pixels
4492      *
4493      * @see #getMinHeight()
4494      * @see #setHeight(int)
4495      *
4496      * @attr ref android.R.styleable#TextView_minHeight
4497      */
4498     @android.view.RemotableViewMethod
4499     public void setMinHeight(int minPixels) {
4500         mMinimum = minPixels;
4501         mMinMode = PIXELS;
4502
4503         requestLayout();
4504         invalidate();
4505     }
4506
4507     /**
4508      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
4509      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
4510      *
4511      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
4512      *         defined in pixels
4513      *
4514      * @see #setMinHeight(int)
4515      * @see #setHeight(int)
4516      *
4517      * @attr ref android.R.styleable#TextView_minHeight
4518      */
4519     public int getMinHeight() {
4520         return mMinMode == PIXELS ? mMinimum : -1;
4521     }
4522
4523     /**
4524      * Sets the height of the TextView to be at most {@code maxLines} tall.
4525      * <p>
4526      * This value is used for height calculation if LayoutParams does not force TextView to have an
4527      * exact height. Setting this value overrides previous maximum height configurations such as
4528      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
4529      *
4530      * @param maxLines the maximum height of TextView in terms of number of lines
4531      *
4532      * @see #getMaxLines()
4533      * @see #setLines(int)
4534      *
4535      * @attr ref android.R.styleable#TextView_maxLines
4536      */
4537     @android.view.RemotableViewMethod
4538     public void setMaxLines(int maxLines) {
4539         mMaximum = maxLines;
4540         mMaxMode = LINES;
4541
4542         requestLayout();
4543         invalidate();
4544     }
4545
4546     /**
4547      * Returns the maximum height of TextView in terms of number of lines or -1 if the
4548      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
4549      *
4550      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
4551      *         is not defined in lines.
4552      *
4553      * @see #setMaxLines(int)
4554      * @see #setLines(int)
4555      *
4556      * @attr ref android.R.styleable#TextView_maxLines
4557      */
4558     public int getMaxLines() {
4559         return mMaxMode == LINES ? mMaximum : -1;
4560     }
4561
4562     /**
4563      * Sets the height of the TextView to be at most {@code maxPixels} tall.
4564      * <p>
4565      * This value is used for height calculation if LayoutParams does not force TextView to have an
4566      * exact height. Setting this value overrides previous maximum height configurations such as
4567      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
4568      *
4569      * @param maxPixels the maximum height of TextView in terms of pixels
4570      *
4571      * @see #getMaxHeight()
4572      * @see #setHeight(int)
4573      *
4574      * @attr ref android.R.styleable#TextView_maxHeight
4575      */
4576     @android.view.RemotableViewMethod
4577     public void setMaxHeight(int maxPixels) {
4578         mMaximum = maxPixels;
4579         mMaxMode = PIXELS;
4580
4581         requestLayout();
4582         invalidate();
4583     }
4584
4585     /**
4586      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
4587      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
4588      *
4589      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
4590      *         is not defined in pixels
4591      *
4592      * @see #setMaxHeight(int)
4593      * @see #setHeight(int)
4594      *
4595      * @attr ref android.R.styleable#TextView_maxHeight
4596      */
4597     public int getMaxHeight() {
4598         return mMaxMode == PIXELS ? mMaximum : -1;
4599     }
4600
4601     /**
4602      * Sets the height of the TextView to be exactly {@code lines} tall.
4603      * <p>
4604      * This value is used for height calculation if LayoutParams does not force TextView to have an
4605      * exact height. Setting this value overrides previous minimum/maximum height configurations
4606      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
4607      * set this value to 1.
4608      *
4609      * @param lines the exact height of the TextView in terms of lines
4610      *
4611      * @see #setHeight(int)
4612      *
4613      * @attr ref android.R.styleable#TextView_lines
4614      */
4615     @android.view.RemotableViewMethod
4616     public void setLines(int lines) {
4617         mMaximum = mMinimum = lines;
4618         mMaxMode = mMinMode = LINES;
4619
4620         requestLayout();
4621         invalidate();
4622     }
4623
4624     /**
4625      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
4626      * <p>
4627      * This value is used for height calculation if LayoutParams does not force TextView to have an
4628      * exact height. Setting this value overrides previous minimum/maximum height configurations
4629      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
4630      *
4631      * @param pixels the exact height of the TextView in terms of pixels
4632      *
4633      * @see #setLines(int)
4634      *
4635      * @attr ref android.R.styleable#TextView_height
4636      */
4637     @android.view.RemotableViewMethod
4638     public void setHeight(int pixels) {
4639         mMaximum = mMinimum = pixels;
4640         mMaxMode = mMinMode = PIXELS;
4641
4642         requestLayout();
4643         invalidate();
4644     }
4645
4646     /**
4647      * Sets the width of the TextView to be at least {@code minEms} wide.
4648      * <p>
4649      * This value is used for width calculation if LayoutParams does not force TextView to have an
4650      * exact width. Setting this value overrides previous minimum width configurations such as
4651      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4652      *
4653      * @param minEms the minimum width of TextView in terms of ems
4654      *
4655      * @see #getMinEms()
4656      * @see #setEms(int)
4657      *
4658      * @attr ref android.R.styleable#TextView_minEms
4659      */
4660     @android.view.RemotableViewMethod
4661     public void setMinEms(int minEms) {
4662         mMinWidth = minEms;
4663         mMinWidthMode = EMS;
4664
4665         requestLayout();
4666         invalidate();
4667     }
4668
4669     /**
4670      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
4671      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4672      *
4673      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
4674      *         defined in ems
4675      *
4676      * @see #setMinEms(int)
4677      * @see #setEms(int)
4678      *
4679      * @attr ref android.R.styleable#TextView_minEms
4680      */
4681     public int getMinEms() {
4682         return mMinWidthMode == EMS ? mMinWidth : -1;
4683     }
4684
4685     /**
4686      * Sets the width of the TextView to be at least {@code minPixels} wide.
4687      * <p>
4688      * This value is used for width calculation if LayoutParams does not force TextView to have an
4689      * exact width. Setting this value overrides previous minimum width configurations such as
4690      * {@link #setMinEms(int)} or {@link #setEms(int)}.
4691      * <p>
4692      * The value given here is different than {@link #setMinimumWidth(int)}. Between
4693      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
4694      * to decide the final width.
4695      *
4696      * @param minPixels the minimum width of TextView in terms of pixels
4697      *
4698      * @see #getMinWidth()
4699      * @see #setWidth(int)
4700      *
4701      * @attr ref android.R.styleable#TextView_minWidth
4702      */
4703     @android.view.RemotableViewMethod
4704     public void setMinWidth(int minPixels) {
4705         mMinWidth = minPixels;
4706         mMinWidthMode = PIXELS;
4707
4708         requestLayout();
4709         invalidate();
4710     }
4711
4712     /**
4713      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
4714      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
4715      *
4716      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
4717      *         defined in pixels
4718      *
4719      * @see #setMinWidth(int)
4720      * @see #setWidth(int)
4721      *
4722      * @attr ref android.R.styleable#TextView_minWidth
4723      */
4724     public int getMinWidth() {
4725         return mMinWidthMode == PIXELS ? mMinWidth : -1;
4726     }
4727
4728     /**
4729      * Sets the width of the TextView to be at most {@code maxEms} wide.
4730      * <p>
4731      * This value is used for width calculation if LayoutParams does not force TextView to have an
4732      * exact width. Setting this value overrides previous maximum width configurations such as
4733      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
4734      *
4735      * @param maxEms the maximum width of TextView in terms of ems
4736      *
4737      * @see #getMaxEms()
4738      * @see #setEms(int)
4739      *
4740      * @attr ref android.R.styleable#TextView_maxEms
4741      */
4742     @android.view.RemotableViewMethod
4743     public void setMaxEms(int maxEms) {
4744         mMaxWidth = maxEms;
4745         mMaxWidthMode = EMS;
4746
4747         requestLayout();
4748         invalidate();
4749     }
4750
4751     /**
4752      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
4753      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
4754      *
4755      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
4756      *         defined in ems
4757      *
4758      * @see #setMaxEms(int)
4759      * @see #setEms(int)
4760      *
4761      * @attr ref android.R.styleable#TextView_maxEms
4762      */
4763     public int getMaxEms() {
4764         return mMaxWidthMode == EMS ? mMaxWidth : -1;
4765     }
4766
4767     /**
4768      * Sets the width of the TextView to be at most {@code maxPixels} wide.
4769      * <p>
4770      * This value is used for width calculation if LayoutParams does not force TextView to have an
4771      * exact width. Setting this value overrides previous maximum width configurations such as
4772      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
4773      *
4774      * @param maxPixels the maximum width of TextView in terms of pixels
4775      *
4776      * @see #getMaxWidth()
4777      * @see #setWidth(int)
4778      *
4779      * @attr ref android.R.styleable#TextView_maxWidth
4780      */
4781     @android.view.RemotableViewMethod
4782     public void setMaxWidth(int maxPixels) {
4783         mMaxWidth = maxPixels;
4784         mMaxWidthMode = PIXELS;
4785
4786         requestLayout();
4787         invalidate();
4788     }
4789
4790     /**
4791      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
4792      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
4793      *
4794      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
4795      *         defined in pixels
4796      *
4797      * @see #setMaxWidth(int)
4798      * @see #setWidth(int)
4799      *
4800      * @attr ref android.R.styleable#TextView_maxWidth
4801      */
4802     public int getMaxWidth() {
4803         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
4804     }
4805
4806     /**
4807      * Sets the width of the TextView to be exactly {@code ems} wide.
4808      *
4809      * This value is used for width calculation if LayoutParams does not force TextView to have an
4810      * exact width. Setting this value overrides previous minimum/maximum configurations such as
4811      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
4812      *
4813      * @param ems the exact width of the TextView in terms of ems
4814      *
4815      * @see #setWidth(int)
4816      *
4817      * @attr ref android.R.styleable#TextView_ems
4818      */
4819     @android.view.RemotableViewMethod
4820     public void setEms(int ems) {
4821         mMaxWidth = mMinWidth = ems;
4822         mMaxWidthMode = mMinWidthMode = EMS;
4823
4824         requestLayout();
4825         invalidate();
4826     }
4827
4828     /**
4829      * Sets the width of the TextView to be exactly {@code pixels} wide.
4830      * <p>
4831      * This value is used for width calculation if LayoutParams does not force TextView to have an
4832      * exact width. Setting this value overrides previous minimum/maximum width configurations
4833      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
4834      *
4835      * @param pixels the exact width of the TextView in terms of pixels
4836      *
4837      * @see #setEms(int)
4838      *
4839      * @attr ref android.R.styleable#TextView_width
4840      */
4841     @android.view.RemotableViewMethod
4842     public void setWidth(int pixels) {
4843         mMaxWidth = mMinWidth = pixels;
4844         mMaxWidthMode = mMinWidthMode = PIXELS;
4845
4846         requestLayout();
4847         invalidate();
4848     }
4849
4850     /**
4851      * Sets line spacing for this TextView.  Each line will have its height
4852      * multiplied by <code>mult</code> and have <code>add</code> added to it.
4853      *
4854      * @attr ref android.R.styleable#TextView_lineSpacingExtra
4855      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4856      */
4857     public void setLineSpacing(float add, float mult) {
4858         if (mSpacingAdd != add || mSpacingMult != mult) {
4859             mSpacingAdd = add;
4860             mSpacingMult = mult;
4861
4862             if (mLayout != null) {
4863                 nullLayouts();
4864                 requestLayout();
4865                 invalidate();
4866             }
4867         }
4868     }
4869
4870     /**
4871      * Gets the line spacing multiplier
4872      *
4873      * @return the value by which each line's height is multiplied to get its actual height.
4874      *
4875      * @see #setLineSpacing(float, float)
4876      * @see #getLineSpacingExtra()
4877      *
4878      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4879      */
4880     public float getLineSpacingMultiplier() {
4881         return mSpacingMult;
4882     }
4883
4884     /**
4885      * Gets the line spacing extra space
4886      *
4887      * @return the extra space that is added to the height of each lines of this TextView.
4888      *
4889      * @see #setLineSpacing(float, float)
4890      * @see #getLineSpacingMultiplier()
4891      *
4892      * @attr ref android.R.styleable#TextView_lineSpacingExtra
4893      */
4894     public float getLineSpacingExtra() {
4895         return mSpacingAdd;
4896     }
4897
4898     /**
4899      * Convenience method to append the specified text to the TextView's
4900      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
4901      * if it was not already editable.
4902      *
4903      * @param text text to be appended to the already displayed text
4904      */
4905     public final void append(CharSequence text) {
4906         append(text, 0, text.length());
4907     }
4908
4909     /**
4910      * Convenience method to append the specified text slice to the TextView's
4911      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
4912      * if it was not already editable.
4913      *
4914      * @param text text to be appended to the already displayed text
4915      * @param start the index of the first character in the {@code text}
4916      * @param end the index of the character following the last character in the {@code text}
4917      *
4918      * @see Appendable#append(CharSequence, int, int)
4919      */
4920     public void append(CharSequence text, int start, int end) {
4921         if (!(mText instanceof Editable)) {
4922             setText(mText, BufferType.EDITABLE);
4923         }
4924
4925         ((Editable) mText).append(text, start, end);
4926
4927         if (mAutoLinkMask != 0) {
4928             boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4929             // Do not change the movement method for text that support text selection as it
4930             // would prevent an arbitrary cursor displacement.
4931             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
4932                 setMovementMethod(LinkMovementMethod.getInstance());
4933             }
4934         }
4935     }
4936
4937     private void updateTextColors() {
4938         boolean inval = false;
4939         int color = mTextColor.getColorForState(getDrawableState(), 0);
4940         if (color != mCurTextColor) {
4941             mCurTextColor = color;
4942             inval = true;
4943         }
4944         if (mLinkTextColor != null) {
4945             color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4946             if (color != mTextPaint.linkColor) {
4947                 mTextPaint.linkColor = color;
4948                 inval = true;
4949             }
4950         }
4951         if (mHintTextColor != null) {
4952             color = mHintTextColor.getColorForState(getDrawableState(), 0);
4953             if (color != mCurHintTextColor) {
4954                 mCurHintTextColor = color;
4955                 if (mText.length() == 0) {
4956                     inval = true;
4957                 }
4958             }
4959         }
4960         if (inval) {
4961             // Text needs to be redrawn with the new color
4962             if (mEditor != null) mEditor.invalidateTextDisplayList();
4963             invalidate();
4964         }
4965     }
4966
4967     @Override
4968     protected void drawableStateChanged() {
4969         super.drawableStateChanged();
4970
4971         if (mTextColor != null && mTextColor.isStateful()
4972                 || (mHintTextColor != null && mHintTextColor.isStateful())
4973                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4974             updateTextColors();
4975         }
4976
4977         if (mDrawables != null) {
4978             final int[] state = getDrawableState();
4979             for (Drawable dr : mDrawables.mShowing) {
4980                 if (dr != null && dr.isStateful() && dr.setState(state)) {
4981                     invalidateDrawable(dr);
4982                 }
4983             }
4984         }
4985     }
4986
4987     @Override
4988     public void drawableHotspotChanged(float x, float y) {
4989         super.drawableHotspotChanged(x, y);
4990
4991         if (mDrawables != null) {
4992             for (Drawable dr : mDrawables.mShowing) {
4993                 if (dr != null) {
4994                     dr.setHotspot(x, y);
4995                 }
4996             }
4997         }
4998     }
4999
5000     @Override
5001     public Parcelable onSaveInstanceState() {
5002         Parcelable superState = super.onSaveInstanceState();
5003
5004         // Save state if we are forced to
5005         final boolean freezesText = getFreezesText();
5006         boolean hasSelection = false;
5007         int start = -1;
5008         int end = -1;
5009
5010         if (mText != null) {
5011             start = getSelectionStart();
5012             end = getSelectionEnd();
5013             if (start >= 0 || end >= 0) {
5014                 // Or save state if there is a selection
5015                 hasSelection = true;
5016             }
5017         }
5018
5019         if (freezesText || hasSelection) {
5020             SavedState ss = new SavedState(superState);
5021
5022             if (freezesText) {
5023                 if (mText instanceof Spanned) {
5024                     final Spannable sp = new SpannableStringBuilder(mText);
5025
5026                     if (mEditor != null) {
5027                         removeMisspelledSpans(sp);
5028                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
5029                     }
5030
5031                     ss.text = sp;
5032                 } else {
5033                     ss.text = mText.toString();
5034                 }
5035             }
5036
5037             if (hasSelection) {
5038                 // XXX Should also save the current scroll position!
5039                 ss.selStart = start;
5040                 ss.selEnd = end;
5041             }
5042
5043             if (isFocused() && start >= 0 && end >= 0) {
5044                 ss.frozenWithFocus = true;
5045             }
5046
5047             ss.error = getError();
5048
5049             if (mEditor != null) {
5050                 ss.editorState = mEditor.saveInstanceState();
5051             }
5052             return ss;
5053         }
5054
5055         return superState;
5056     }
5057
5058     void removeMisspelledSpans(Spannable spannable) {
5059         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5060                 SuggestionSpan.class);
5061         for (int i = 0; i < suggestionSpans.length; i++) {
5062             int flags = suggestionSpans[i].getFlags();
5063             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5064                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5065                 spannable.removeSpan(suggestionSpans[i]);
5066             }
5067         }
5068     }
5069
5070     @Override
5071     public void onRestoreInstanceState(Parcelable state) {
5072         if (!(state instanceof SavedState)) {
5073             super.onRestoreInstanceState(state);
5074             return;
5075         }
5076
5077         SavedState ss = (SavedState) state;
5078         super.onRestoreInstanceState(ss.getSuperState());
5079
5080         // XXX restore buffer type too, as well as lots of other stuff
5081         if (ss.text != null) {
5082             setText(ss.text);
5083         }
5084
5085         if (ss.selStart >= 0 && ss.selEnd >= 0) {
5086             if (mText instanceof Spannable) {
5087                 int len = mText.length();
5088
5089                 if (ss.selStart > len || ss.selEnd > len) {
5090                     String restored = "";
5091
5092                     if (ss.text != null) {
5093                         restored = "(restored) ";
5094                     }
5095
5096                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5097                             + " out of range for " + restored + "text " + mText);
5098                 } else {
5099                     Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
5100
5101                     if (ss.frozenWithFocus) {
5102                         createEditorIfNeeded();
5103                         mEditor.mFrozenWithFocus = true;
5104                     }
5105                 }
5106             }
5107         }
5108
5109         if (ss.error != null) {
5110             final CharSequence error = ss.error;
5111             // Display the error later, after the first layout pass
5112             post(new Runnable() {
5113                 public void run() {
5114                     if (mEditor == null || !mEditor.mErrorWasChanged) {
5115                         setError(error);
5116                     }
5117                 }
5118             });
5119         }
5120
5121         if (ss.editorState != null) {
5122             createEditorIfNeeded();
5123             mEditor.restoreInstanceState(ss.editorState);
5124         }
5125     }
5126
5127     /**
5128      * Control whether this text view saves its entire text contents when
5129      * freezing to an icicle, in addition to dynamic state such as cursor
5130      * position.  By default this is false, not saving the text.  Set to true
5131      * if the text in the text view is not being saved somewhere else in
5132      * persistent storage (such as in a content provider) so that if the
5133      * view is later thawed the user will not lose their data. For
5134      * {@link android.widget.EditText} it is always enabled, regardless of
5135      * the value of the attribute.
5136      *
5137      * @param freezesText Controls whether a frozen icicle should include the
5138      * entire text data: true to include it, false to not.
5139      *
5140      * @attr ref android.R.styleable#TextView_freezesText
5141      */
5142     @android.view.RemotableViewMethod
5143     public void setFreezesText(boolean freezesText) {
5144         mFreezesText = freezesText;
5145     }
5146
5147     /**
5148      * Return whether this text view is including its entire text contents
5149      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5150      *
5151      * @return Returns true if text is included, false if it isn't.
5152      *
5153      * @see #setFreezesText
5154      */
5155     public boolean getFreezesText() {
5156         return mFreezesText;
5157     }
5158
5159     ///////////////////////////////////////////////////////////////////////////
5160
5161     /**
5162      * Sets the Factory used to create new {@link Editable Editables}.
5163      *
5164      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
5165      *
5166      * @see android.text.Editable.Factory
5167      * @see android.widget.TextView.BufferType#EDITABLE
5168      */
5169     public final void setEditableFactory(Editable.Factory factory) {
5170         mEditableFactory = factory;
5171         setText(mText);
5172     }
5173
5174     /**
5175      * Sets the Factory used to create new {@link Spannable Spannables}.
5176      *
5177      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
5178      *
5179      * @see android.text.Spannable.Factory
5180      * @see android.widget.TextView.BufferType#SPANNABLE
5181      */
5182     public final void setSpannableFactory(Spannable.Factory factory) {
5183         mSpannableFactory = factory;
5184         setText(mText);
5185     }
5186
5187     /**
5188      * Sets the text to be displayed. TextView <em>does not</em> accept
5189      * HTML-like formatting, which you can do with text strings in XML resource files.
5190      * To style your strings, attach android.text.style.* objects to a
5191      * {@link android.text.SpannableString}, or see the
5192      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
5193      * Available Resource Types</a> documentation for an example of setting
5194      * formatted text in the XML resource file.
5195      * <p/>
5196      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5197      * intermediate {@link Spannable Spannables}. Likewise it will use
5198      * {@link android.text.Editable.Factory} to create final or intermediate
5199      * {@link Editable Editables}.
5200      *
5201      * @param text text to be displayed
5202      *
5203      * @attr ref android.R.styleable#TextView_text
5204      */
5205     @android.view.RemotableViewMethod
5206     public final void setText(CharSequence text) {
5207         setText(text, mBufferType);
5208     }
5209
5210     /**
5211      * Sets the text to be displayed but retains the cursor position. Same as
5212      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
5213      * new text.
5214      * <p/>
5215      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5216      * intermediate {@link Spannable Spannables}. Likewise it will use
5217      * {@link android.text.Editable.Factory} to create final or intermediate
5218      * {@link Editable Editables}.
5219      *
5220      * @param text text to be displayed
5221      *
5222      * @see #setText(CharSequence)
5223      */
5224     @android.view.RemotableViewMethod
5225     public final void setTextKeepState(CharSequence text) {
5226         setTextKeepState(text, mBufferType);
5227     }
5228
5229     /**
5230      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
5231      * <p/>
5232      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5233      * intermediate {@link Spannable Spannables}. Likewise it will use
5234      * {@link android.text.Editable.Factory} to create final or intermediate
5235      * {@link Editable Editables}.
5236      *
5237      * @param text text to be displayed
5238      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5239      *              stored as a static text, styleable/spannable text, or editable text
5240      *
5241      * @see #setText(CharSequence)
5242      * @see android.widget.TextView.BufferType
5243      * @see #setSpannableFactory(Spannable.Factory)
5244      * @see #setEditableFactory(Editable.Factory)
5245      *
5246      * @attr ref android.R.styleable#TextView_text
5247      * @attr ref android.R.styleable#TextView_bufferType
5248      */
5249     public void setText(CharSequence text, BufferType type) {
5250         setText(text, type, true, 0);
5251
5252         if (mCharWrapper != null) {
5253             mCharWrapper.mChars = null;
5254         }
5255     }
5256
5257     private void setText(CharSequence text, BufferType type,
5258                          boolean notifyBefore, int oldlen) {
5259         mTextFromResource = false;
5260         if (text == null) {
5261             text = "";
5262         }
5263
5264         // If suggestions are not enabled, remove the suggestion spans from the text
5265         if (!isSuggestionsEnabled()) {
5266             text = removeSuggestionSpans(text);
5267         }
5268
5269         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
5270
5271         if (text instanceof Spanned
5272                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
5273             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
5274                 setHorizontalFadingEdgeEnabled(true);
5275                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
5276             } else {
5277                 setHorizontalFadingEdgeEnabled(false);
5278                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
5279             }
5280             setEllipsize(TextUtils.TruncateAt.MARQUEE);
5281         }
5282
5283         int n = mFilters.length;
5284         for (int i = 0; i < n; i++) {
5285             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
5286             if (out != null) {
5287                 text = out;
5288             }
5289         }
5290
5291         if (notifyBefore) {
5292             if (mText != null) {
5293                 oldlen = mText.length();
5294                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
5295             } else {
5296                 sendBeforeTextChanged("", 0, 0, text.length());
5297             }
5298         }
5299
5300         boolean needEditableForNotification = false;
5301
5302         if (mListeners != null && mListeners.size() != 0) {
5303             needEditableForNotification = true;
5304         }
5305
5306         if (type == BufferType.EDITABLE || getKeyListener() != null
5307                 || needEditableForNotification) {
5308             createEditorIfNeeded();
5309             mEditor.forgetUndoRedo();
5310             Editable t = mEditableFactory.newEditable(text);
5311             text = t;
5312             setFilters(t, mFilters);
5313             InputMethodManager imm = InputMethodManager.peekInstance();
5314             if (imm != null) imm.restartInput(this);
5315         } else if (type == BufferType.SPANNABLE || mMovement != null) {
5316             text = mSpannableFactory.newSpannable(text);
5317         } else if (!(text instanceof CharWrapper)) {
5318             text = TextUtils.stringOrSpannedString(text);
5319         }
5320
5321         if (mAutoLinkMask != 0) {
5322             Spannable s2;
5323
5324             if (type == BufferType.EDITABLE || text instanceof Spannable) {
5325                 s2 = (Spannable) text;
5326             } else {
5327                 s2 = mSpannableFactory.newSpannable(text);
5328             }
5329
5330             if (Linkify.addLinks(s2, mAutoLinkMask)) {
5331                 text = s2;
5332                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
5333
5334                 /*
5335                  * We must go ahead and set the text before changing the
5336                  * movement method, because setMovementMethod() may call
5337                  * setText() again to try to upgrade the buffer type.
5338                  */
5339                 mText = text;
5340
5341                 // Do not change the movement method for text that support text selection as it
5342                 // would prevent an arbitrary cursor displacement.
5343                 if (mLinksClickable && !textCanBeSelected()) {
5344                     setMovementMethod(LinkMovementMethod.getInstance());
5345                 }
5346             }
5347         }
5348
5349         mBufferType = type;
5350         mText = text;
5351
5352         if (mTransformation == null) {
5353             mTransformed = text;
5354         } else {
5355             mTransformed = mTransformation.getTransformation(text, this);
5356         }
5357
5358         final int textLength = text.length();
5359
5360         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
5361             Spannable sp = (Spannable) text;
5362
5363             // Remove any ChangeWatchers that might have come from other TextViews.
5364             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
5365             final int count = watchers.length;
5366             for (int i = 0; i < count; i++) {
5367                 sp.removeSpan(watchers[i]);
5368             }
5369
5370             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
5371
5372             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
5373                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
5374
5375             if (mEditor != null) mEditor.addSpanWatchers(sp);
5376
5377             if (mTransformation != null) {
5378                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
5379             }
5380
5381             if (mMovement != null) {
5382                 mMovement.initialize(this, (Spannable) text);
5383
5384                 /*
5385                  * Initializing the movement method will have set the
5386                  * selection, so reset mSelectionMoved to keep that from
5387                  * interfering with the normal on-focus selection-setting.
5388                  */
5389                 if (mEditor != null) mEditor.mSelectionMoved = false;
5390             }
5391         }
5392
5393         if (mLayout != null) {
5394             checkForRelayout();
5395         }
5396
5397         sendOnTextChanged(text, 0, oldlen, textLength);
5398         onTextChanged(text, 0, oldlen, textLength);
5399
5400         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
5401
5402         if (needEditableForNotification) {
5403             sendAfterTextChanged((Editable) text);
5404         } else {
5405             // Always notify AutoFillManager - it will return right away if autofill is disabled.
5406             notifyAutoFillManagerAfterTextChangedIfNeeded();
5407         }
5408
5409         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
5410         if (mEditor != null) mEditor.prepareCursorControllers();
5411     }
5412
5413     /**
5414      * Sets the TextView to display the specified slice of the specified
5415      * char array. You must promise that you will not change the contents
5416      * of the array except for right before another call to setText(),
5417      * since the TextView has no way to know that the text
5418      * has changed and that it needs to invalidate and re-layout.
5419      *
5420      * @param text char array to be displayed
5421      * @param start start index in the char array
5422      * @param len length of char count after {@code start}
5423      */
5424     public final void setText(char[] text, int start, int len) {
5425         int oldlen = 0;
5426
5427         if (start < 0 || len < 0 || start + len > text.length) {
5428             throw new IndexOutOfBoundsException(start + ", " + len);
5429         }
5430
5431         /*
5432          * We must do the before-notification here ourselves because if
5433          * the old text is a CharWrapper we destroy it before calling
5434          * into the normal path.
5435          */
5436         if (mText != null) {
5437             oldlen = mText.length();
5438             sendBeforeTextChanged(mText, 0, oldlen, len);
5439         } else {
5440             sendBeforeTextChanged("", 0, 0, len);
5441         }
5442
5443         if (mCharWrapper == null) {
5444             mCharWrapper = new CharWrapper(text, start, len);
5445         } else {
5446             mCharWrapper.set(text, start, len);
5447         }
5448
5449         setText(mCharWrapper, mBufferType, false, oldlen);
5450     }
5451
5452     /**
5453      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
5454      * the cursor position. Same as
5455      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
5456      * position (if any) is retained in the new text.
5457      * <p/>
5458      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5459      * intermediate {@link Spannable Spannables}. Likewise it will use
5460      * {@link android.text.Editable.Factory} to create final or intermediate
5461      * {@link Editable Editables}.
5462      *
5463      * @param text text to be displayed
5464      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5465      *              stored as a static text, styleable/spannable text, or editable text
5466      *
5467      * @see #setText(CharSequence, android.widget.TextView.BufferType)
5468      */
5469     public final void setTextKeepState(CharSequence text, BufferType type) {
5470         int start = getSelectionStart();
5471         int end = getSelectionEnd();
5472         int len = text.length();
5473
5474         setText(text, type);
5475
5476         if (start >= 0 || end >= 0) {
5477             if (mText instanceof Spannable) {
5478                 Selection.setSelection((Spannable) mText,
5479                                        Math.max(0, Math.min(start, len)),
5480                                        Math.max(0, Math.min(end, len)));
5481             }
5482         }
5483     }
5484
5485     /**
5486      * Sets the text to be displayed using a string resource identifier.
5487      *
5488      * @param resid the resource identifier of the string resource to be displayed
5489      *
5490      * @see #setText(CharSequence)
5491      *
5492      * @attr ref android.R.styleable#TextView_text
5493      */
5494     @android.view.RemotableViewMethod
5495     public final void setText(@StringRes int resid) {
5496         setText(getContext().getResources().getText(resid));
5497         mTextFromResource = true;
5498     }
5499
5500     /**
5501      * Sets the text to be displayed using a string resource identifier and the
5502      * {@link android.widget.TextView.BufferType}.
5503      * <p/>
5504      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5505      * intermediate {@link Spannable Spannables}. Likewise it will use
5506      * {@link android.text.Editable.Factory} to create final or intermediate
5507      * {@link Editable Editables}.
5508      *
5509      * @param resid the resource identifier of the string resource to be displayed
5510      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5511      *              stored as a static text, styleable/spannable text, or editable text
5512      *
5513      * @see #setText(int)
5514      * @see #setText(CharSequence)
5515      * @see android.widget.TextView.BufferType
5516      * @see #setSpannableFactory(Spannable.Factory)
5517      * @see #setEditableFactory(Editable.Factory)
5518      *
5519      * @attr ref android.R.styleable#TextView_text
5520      * @attr ref android.R.styleable#TextView_bufferType
5521      */
5522     public final void setText(@StringRes int resid, BufferType type) {
5523         setText(getContext().getResources().getText(resid), type);
5524         mTextFromResource = true;
5525     }
5526
5527     /**
5528      * Sets the text to be displayed when the text of the TextView is empty.
5529      * Null means to use the normal empty text. The hint does not currently
5530      * participate in determining the size of the view.
5531      *
5532      * @attr ref android.R.styleable#TextView_hint
5533      */
5534     @android.view.RemotableViewMethod
5535     public final void setHint(CharSequence hint) {
5536         mHint = TextUtils.stringOrSpannedString(hint);
5537
5538         if (mLayout != null) {
5539             checkForRelayout();
5540         }
5541
5542         if (mText.length() == 0) {
5543             invalidate();
5544         }
5545
5546         // Invalidate display list if hint is currently used
5547         if (mEditor != null && mText.length() == 0 && mHint != null) {
5548             mEditor.invalidateTextDisplayList();
5549         }
5550     }
5551
5552     /**
5553      * Sets the text to be displayed when the text of the TextView is empty,
5554      * from a resource.
5555      *
5556      * @attr ref android.R.styleable#TextView_hint
5557      */
5558     @android.view.RemotableViewMethod
5559     public final void setHint(@StringRes int resid) {
5560         setHint(getContext().getResources().getText(resid));
5561     }
5562
5563     /**
5564      * Returns the hint that is displayed when the text of the TextView
5565      * is empty.
5566      *
5567      * @attr ref android.R.styleable#TextView_hint
5568      */
5569     @ViewDebug.CapturedViewProperty
5570     public CharSequence getHint() {
5571         return mHint;
5572     }
5573
5574     boolean isSingleLine() {
5575         return mSingleLine;
5576     }
5577
5578     private static boolean isMultilineInputType(int type) {
5579         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
5580                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
5581     }
5582
5583     /**
5584      * Removes the suggestion spans.
5585      */
5586     CharSequence removeSuggestionSpans(CharSequence text) {
5587         if (text instanceof Spanned) {
5588             Spannable spannable;
5589             if (text instanceof Spannable) {
5590                 spannable = (Spannable) text;
5591             } else {
5592                 spannable = mSpannableFactory.newSpannable(text);
5593                 text = spannable;
5594             }
5595
5596             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
5597             for (int i = 0; i < spans.length; i++) {
5598                 spannable.removeSpan(spans[i]);
5599             }
5600         }
5601         return text;
5602     }
5603
5604     /**
5605      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
5606      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
5607      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
5608      * then a soft keyboard will not be displayed for this text view.
5609      *
5610      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
5611      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
5612      * type.
5613      *
5614      * @see #getInputType()
5615      * @see #setRawInputType(int)
5616      * @see android.text.InputType
5617      * @attr ref android.R.styleable#TextView_inputType
5618      */
5619     public void setInputType(int type) {
5620         final boolean wasPassword = isPasswordInputType(getInputType());
5621         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
5622         setInputType(type, false);
5623         final boolean isPassword = isPasswordInputType(type);
5624         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
5625         boolean forceUpdate = false;
5626         if (isPassword) {
5627             setTransformationMethod(PasswordTransformationMethod.getInstance());
5628             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5629         } else if (isVisiblePassword) {
5630             if (mTransformation == PasswordTransformationMethod.getInstance()) {
5631                 forceUpdate = true;
5632             }
5633             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5634         } else if (wasPassword || wasVisiblePassword) {
5635             // not in password mode, clean up typeface and transformation
5636             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
5637             if (mTransformation == PasswordTransformationMethod.getInstance()) {
5638                 forceUpdate = true;
5639             }
5640         }
5641
5642         boolean singleLine = !isMultilineInputType(type);
5643
5644         // We need to update the single line mode if it has changed or we
5645         // were previously in password mode.
5646         if (mSingleLine != singleLine || forceUpdate) {
5647             // Change single line mode, but only change the transformation if
5648             // we are not in password mode.
5649             applySingleLine(singleLine, !isPassword, true);
5650         }
5651
5652         if (!isSuggestionsEnabled()) {
5653             mText = removeSuggestionSpans(mText);
5654         }
5655
5656         InputMethodManager imm = InputMethodManager.peekInstance();
5657         if (imm != null) imm.restartInput(this);
5658     }
5659
5660     /**
5661      * It would be better to rely on the input type for everything. A password inputType should have
5662      * a password transformation. We should hence use isPasswordInputType instead of this method.
5663      *
5664      * We should:
5665      * - Call setInputType in setKeyListener instead of changing the input type directly (which
5666      * would install the correct transformation).
5667      * - Refuse the installation of a non-password transformation in setTransformation if the input
5668      * type is password.
5669      *
5670      * However, this is like this for legacy reasons and we cannot break existing apps. This method
5671      * is useful since it matches what the user can see (obfuscated text or not).
5672      *
5673      * @return true if the current transformation method is of the password type.
5674      */
5675     boolean hasPasswordTransformationMethod() {
5676         return mTransformation instanceof PasswordTransformationMethod;
5677     }
5678
5679     static boolean isPasswordInputType(int inputType) {
5680         final int variation =
5681                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5682         return variation
5683                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
5684                 || variation
5685                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
5686                 || variation
5687                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
5688     }
5689
5690     private static boolean isVisiblePasswordInputType(int inputType) {
5691         final int variation =
5692                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5693         return variation
5694                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
5695     }
5696
5697     /**
5698      * Directly change the content type integer of the text view, without
5699      * modifying any other state.
5700      * @see #setInputType(int)
5701      * @see android.text.InputType
5702      * @attr ref android.R.styleable#TextView_inputType
5703      */
5704     public void setRawInputType(int type) {
5705         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
5706         createEditorIfNeeded();
5707         mEditor.mInputType = type;
5708     }
5709
5710     /**
5711      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
5712      *         a {@code Locale} object that can be used to customize key various listeners.
5713      * @see DateKeyListener#getInstance(Locale)
5714      * @see DateTimeKeyListener#getInstance(Locale)
5715      * @see DigitsKeyListener#getInstance(Locale)
5716      * @see TimeKeyListener#getInstance(Locale)
5717      */
5718     @Nullable
5719     private Locale getCustomLocaleForKeyListenerOrNull() {
5720         if (!mUseInternationalizedInput) {
5721             // If the application does not target O, stick to the previous behavior.
5722             return null;
5723         }
5724         final LocaleList locales = getImeHintLocales();
5725         if (locales == null) {
5726             // If the application does not explicitly specify IME hint locale, also stick to the
5727             // previous behavior.
5728             return null;
5729         }
5730         return locales.get(0);
5731     }
5732
5733     private void setInputType(int type, boolean direct) {
5734         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
5735         KeyListener input;
5736         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
5737             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
5738             TextKeyListener.Capitalize cap;
5739             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
5740                 cap = TextKeyListener.Capitalize.CHARACTERS;
5741             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
5742                 cap = TextKeyListener.Capitalize.WORDS;
5743             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
5744                 cap = TextKeyListener.Capitalize.SENTENCES;
5745             } else {
5746                 cap = TextKeyListener.Capitalize.NONE;
5747             }
5748             input = TextKeyListener.getInstance(autotext, cap);
5749         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
5750             final Locale locale = getCustomLocaleForKeyListenerOrNull();
5751             input = DigitsKeyListener.getInstance(
5752                     locale,
5753                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
5754                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
5755             if (locale != null) {
5756                 // Override type, if necessary for i18n.
5757                 int newType = input.getInputType();
5758                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
5759                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
5760                     // The class is different from the original class. So we need to override
5761                     // 'type'. But we want to keep the password flag if it's there.
5762                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
5763                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
5764                     }
5765                     type = newType;
5766                 }
5767             }
5768         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
5769             final Locale locale = getCustomLocaleForKeyListenerOrNull();
5770             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
5771                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
5772                     input = DateKeyListener.getInstance(locale);
5773                     break;
5774                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
5775                     input = TimeKeyListener.getInstance(locale);
5776                     break;
5777                 default:
5778                     input = DateTimeKeyListener.getInstance(locale);
5779                     break;
5780             }
5781             if (mUseInternationalizedInput) {
5782                 type = input.getInputType(); // Override type, if necessary for i18n.
5783             }
5784         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
5785             input = DialerKeyListener.getInstance();
5786         } else {
5787             input = TextKeyListener.getInstance();
5788         }
5789         setRawInputType(type);
5790         mListenerChanged = false;
5791         if (direct) {
5792             createEditorIfNeeded();
5793             mEditor.mKeyListener = input;
5794         } else {
5795             setKeyListenerOnly(input);
5796         }
5797     }
5798
5799     /**
5800      * Get the type of the editable content.
5801      *
5802      * @see #setInputType(int)
5803      * @see android.text.InputType
5804      */
5805     public int getInputType() {
5806         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
5807     }
5808
5809     /**
5810      * Change the editor type integer associated with the text view, which
5811      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
5812      * when it has focus.
5813      * @see #getImeOptions
5814      * @see android.view.inputmethod.EditorInfo
5815      * @attr ref android.R.styleable#TextView_imeOptions
5816      */
5817     public void setImeOptions(int imeOptions) {
5818         createEditorIfNeeded();
5819         mEditor.createInputContentTypeIfNeeded();
5820         mEditor.mInputContentType.imeOptions = imeOptions;
5821     }
5822
5823     /**
5824      * Get the type of the Input Method Editor (IME).
5825      * @return the type of the IME
5826      * @see #setImeOptions(int)
5827      * @see android.view.inputmethod.EditorInfo
5828      */
5829     public int getImeOptions() {
5830         return mEditor != null && mEditor.mInputContentType != null
5831                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
5832     }
5833
5834     /**
5835      * Change the custom IME action associated with the text view, which
5836      * will be reported to an IME with {@link EditorInfo#actionLabel}
5837      * and {@link EditorInfo#actionId} when it has focus.
5838      * @see #getImeActionLabel
5839      * @see #getImeActionId
5840      * @see android.view.inputmethod.EditorInfo
5841      * @attr ref android.R.styleable#TextView_imeActionLabel
5842      * @attr ref android.R.styleable#TextView_imeActionId
5843      */
5844     public void setImeActionLabel(CharSequence label, int actionId) {
5845         createEditorIfNeeded();
5846         mEditor.createInputContentTypeIfNeeded();
5847         mEditor.mInputContentType.imeActionLabel = label;
5848         mEditor.mInputContentType.imeActionId = actionId;
5849     }
5850
5851     /**
5852      * Get the IME action label previous set with {@link #setImeActionLabel}.
5853      *
5854      * @see #setImeActionLabel
5855      * @see android.view.inputmethod.EditorInfo
5856      */
5857     public CharSequence getImeActionLabel() {
5858         return mEditor != null && mEditor.mInputContentType != null
5859                 ? mEditor.mInputContentType.imeActionLabel : null;
5860     }
5861
5862     /**
5863      * Get the IME action ID previous set with {@link #setImeActionLabel}.
5864      *
5865      * @see #setImeActionLabel
5866      * @see android.view.inputmethod.EditorInfo
5867      */
5868     public int getImeActionId() {
5869         return mEditor != null && mEditor.mInputContentType != null
5870                 ? mEditor.mInputContentType.imeActionId : 0;
5871     }
5872
5873     /**
5874      * Set a special listener to be called when an action is performed
5875      * on the text view.  This will be called when the enter key is pressed,
5876      * or when an action supplied to the IME is selected by the user.  Setting
5877      * this means that the normal hard key event will not insert a newline
5878      * into the text view, even if it is multi-line; holding down the ALT
5879      * modifier will, however, allow the user to insert a newline character.
5880      */
5881     public void setOnEditorActionListener(OnEditorActionListener l) {
5882         createEditorIfNeeded();
5883         mEditor.createInputContentTypeIfNeeded();
5884         mEditor.mInputContentType.onEditorActionListener = l;
5885     }
5886
5887     /**
5888      * Called when an attached input method calls
5889      * {@link InputConnection#performEditorAction(int)
5890      * InputConnection.performEditorAction()}
5891      * for this text view.  The default implementation will call your action
5892      * listener supplied to {@link #setOnEditorActionListener}, or perform
5893      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
5894      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
5895      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
5896      * EditorInfo.IME_ACTION_DONE}.
5897      *
5898      * <p>For backwards compatibility, if no IME options have been set and the
5899      * text view would not normally advance focus on enter, then
5900      * the NEXT and DONE actions received here will be turned into an enter
5901      * key down/up pair to go through the normal key handling.
5902      *
5903      * @param actionCode The code of the action being performed.
5904      *
5905      * @see #setOnEditorActionListener
5906      */
5907     public void onEditorAction(int actionCode) {
5908         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
5909         if (ict != null) {
5910             if (ict.onEditorActionListener != null) {
5911                 if (ict.onEditorActionListener.onEditorAction(this,
5912                         actionCode, null)) {
5913                     return;
5914                 }
5915             }
5916
5917             // This is the handling for some default action.
5918             // Note that for backwards compatibility we don't do this
5919             // default handling if explicit ime options have not been given,
5920             // instead turning this into the normal enter key codes that an
5921             // app may be expecting.
5922             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
5923                 View v = focusSearch(FOCUS_FORWARD);
5924                 if (v != null) {
5925                     if (!v.requestFocus(FOCUS_FORWARD)) {
5926                         throw new IllegalStateException("focus search returned a view "
5927                                 + "that wasn't able to take focus!");
5928                     }
5929                 }
5930                 return;
5931
5932             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
5933                 View v = focusSearch(FOCUS_BACKWARD);
5934                 if (v != null) {
5935                     if (!v.requestFocus(FOCUS_BACKWARD)) {
5936                         throw new IllegalStateException("focus search returned a view "
5937                                 + "that wasn't able to take focus!");
5938                     }
5939                 }
5940                 return;
5941
5942             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
5943                 InputMethodManager imm = InputMethodManager.peekInstance();
5944                 if (imm != null && imm.isActive(this)) {
5945                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
5946                 }
5947                 return;
5948             }
5949         }
5950
5951         ViewRootImpl viewRootImpl = getViewRootImpl();
5952         if (viewRootImpl != null) {
5953             long eventTime = SystemClock.uptimeMillis();
5954             viewRootImpl.dispatchKeyFromIme(
5955                     new KeyEvent(eventTime, eventTime,
5956                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
5957                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
5958                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
5959                     | KeyEvent.FLAG_EDITOR_ACTION));
5960             viewRootImpl.dispatchKeyFromIme(
5961                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
5962                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
5963                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
5964                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
5965                     | KeyEvent.FLAG_EDITOR_ACTION));
5966         }
5967     }
5968
5969     /**
5970      * Set the private content type of the text, which is the
5971      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
5972      * field that will be filled in when creating an input connection.
5973      *
5974      * @see #getPrivateImeOptions()
5975      * @see EditorInfo#privateImeOptions
5976      * @attr ref android.R.styleable#TextView_privateImeOptions
5977      */
5978     public void setPrivateImeOptions(String type) {
5979         createEditorIfNeeded();
5980         mEditor.createInputContentTypeIfNeeded();
5981         mEditor.mInputContentType.privateImeOptions = type;
5982     }
5983
5984     /**
5985      * Get the private type of the content.
5986      *
5987      * @see #setPrivateImeOptions(String)
5988      * @see EditorInfo#privateImeOptions
5989      */
5990     public String getPrivateImeOptions() {
5991         return mEditor != null && mEditor.mInputContentType != null
5992                 ? mEditor.mInputContentType.privateImeOptions : null;
5993     }
5994
5995     /**
5996      * Set the extra input data of the text, which is the
5997      * {@link EditorInfo#extras TextBoxAttribute.extras}
5998      * Bundle that will be filled in when creating an input connection.  The
5999      * given integer is the resource identifier of an XML resource holding an
6000      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
6001      *
6002      * @see #getInputExtras(boolean)
6003      * @see EditorInfo#extras
6004      * @attr ref android.R.styleable#TextView_editorExtras
6005      */
6006     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
6007         createEditorIfNeeded();
6008         XmlResourceParser parser = getResources().getXml(xmlResId);
6009         mEditor.createInputContentTypeIfNeeded();
6010         mEditor.mInputContentType.extras = new Bundle();
6011         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
6012     }
6013
6014     /**
6015      * Retrieve the input extras currently associated with the text view, which
6016      * can be viewed as well as modified.
6017      *
6018      * @param create If true, the extras will be created if they don't already
6019      * exist.  Otherwise, null will be returned if none have been created.
6020      * @see #setInputExtras(int)
6021      * @see EditorInfo#extras
6022      * @attr ref android.R.styleable#TextView_editorExtras
6023      */
6024     public Bundle getInputExtras(boolean create) {
6025         if (mEditor == null && !create) return null;
6026         createEditorIfNeeded();
6027         if (mEditor.mInputContentType == null) {
6028             if (!create) return null;
6029             mEditor.createInputContentTypeIfNeeded();
6030         }
6031         if (mEditor.mInputContentType.extras == null) {
6032             if (!create) return null;
6033             mEditor.mInputContentType.extras = new Bundle();
6034         }
6035         return mEditor.mInputContentType.extras;
6036     }
6037
6038     /**
6039      * Change "hint" locales associated with the text view, which will be reported to an IME with
6040      * {@link EditorInfo#hintLocales} when it has focus.
6041      *
6042      * Starting with Android O, this also causes internationalized listeners to be created (or
6043      * change locale) based on the first locale in the input locale list.
6044      *
6045      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
6046      * call {@link InputMethodManager#restartInput(View)}.</p>
6047      * @param hintLocales List of the languages that the user is supposed to switch to no matter
6048      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
6049      * @see #getImeHintLocales()
6050      * @see android.view.inputmethod.EditorInfo#hintLocales
6051      */
6052     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
6053         createEditorIfNeeded();
6054         mEditor.createInputContentTypeIfNeeded();
6055         mEditor.mInputContentType.imeHintLocales = hintLocales;
6056         if (mUseInternationalizedInput) {
6057             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
6058         }
6059     }
6060
6061     /**
6062      * @return The current languages list "hint". {@code null} when no "hint" is available.
6063      * @see #setImeHintLocales(LocaleList)
6064      * @see android.view.inputmethod.EditorInfo#hintLocales
6065      */
6066     @Nullable
6067     public LocaleList getImeHintLocales() {
6068         if (mEditor == null) {
6069             return null;
6070         }
6071         if (mEditor.mInputContentType == null) {
6072             return null;
6073         }
6074         return mEditor.mInputContentType.imeHintLocales;
6075     }
6076
6077     /**
6078      * Returns the error message that was set to be displayed with
6079      * {@link #setError}, or <code>null</code> if no error was set
6080      * or if it the error was cleared by the widget after user input.
6081      */
6082     public CharSequence getError() {
6083         return mEditor == null ? null : mEditor.mError;
6084     }
6085
6086     /**
6087      * Sets the right-hand compound drawable of the TextView to the "error"
6088      * icon and sets an error message that will be displayed in a popup when
6089      * the TextView has focus.  The icon and error message will be reset to
6090      * null when any key events cause changes to the TextView's text.  If the
6091      * <code>error</code> is <code>null</code>, the error message and icon
6092      * will be cleared.
6093      */
6094     @android.view.RemotableViewMethod
6095     public void setError(CharSequence error) {
6096         if (error == null) {
6097             setError(null, null);
6098         } else {
6099             Drawable dr = getContext().getDrawable(
6100                     com.android.internal.R.drawable.indicator_input_error);
6101
6102             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
6103             setError(error, dr);
6104         }
6105     }
6106
6107     /**
6108      * Sets the right-hand compound drawable of the TextView to the specified
6109      * icon and sets an error message that will be displayed in a popup when
6110      * the TextView has focus.  The icon and error message will be reset to
6111      * null when any key events cause changes to the TextView's text.  The
6112      * drawable must already have had {@link Drawable#setBounds} set on it.
6113      * If the <code>error</code> is <code>null</code>, the error message will
6114      * be cleared (and you should provide a <code>null</code> icon as well).
6115      */
6116     public void setError(CharSequence error, Drawable icon) {
6117         createEditorIfNeeded();
6118         mEditor.setError(error, icon);
6119         notifyViewAccessibilityStateChangedIfNeeded(
6120                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
6121     }
6122
6123     @Override
6124     protected boolean setFrame(int l, int t, int r, int b) {
6125         boolean result = super.setFrame(l, t, r, b);
6126
6127         if (mEditor != null) mEditor.setFrame();
6128
6129         restartMarqueeIfNeeded();
6130
6131         return result;
6132     }
6133
6134     private void restartMarqueeIfNeeded() {
6135         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6136             mRestartMarquee = false;
6137             startMarquee();
6138         }
6139     }
6140
6141     /**
6142      * Sets the list of input filters that will be used if the buffer is
6143      * Editable. Has no effect otherwise.
6144      *
6145      * @attr ref android.R.styleable#TextView_maxLength
6146      */
6147     public void setFilters(InputFilter[] filters) {
6148         if (filters == null) {
6149             throw new IllegalArgumentException();
6150         }
6151
6152         mFilters = filters;
6153
6154         if (mText instanceof Editable) {
6155             setFilters((Editable) mText, filters);
6156         }
6157     }
6158
6159     /**
6160      * Sets the list of input filters on the specified Editable,
6161      * and includes mInput in the list if it is an InputFilter.
6162      */
6163     private void setFilters(Editable e, InputFilter[] filters) {
6164         if (mEditor != null) {
6165             final boolean undoFilter = mEditor.mUndoInputFilter != null;
6166             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
6167             int num = 0;
6168             if (undoFilter) num++;
6169             if (keyFilter) num++;
6170             if (num > 0) {
6171                 InputFilter[] nf = new InputFilter[filters.length + num];
6172
6173                 System.arraycopy(filters, 0, nf, 0, filters.length);
6174                 num = 0;
6175                 if (undoFilter) {
6176                     nf[filters.length] = mEditor.mUndoInputFilter;
6177                     num++;
6178                 }
6179                 if (keyFilter) {
6180                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
6181                 }
6182
6183                 e.setFilters(nf);
6184                 return;
6185             }
6186         }
6187         e.setFilters(filters);
6188     }
6189
6190     /**
6191      * Returns the current list of input filters.
6192      *
6193      * @attr ref android.R.styleable#TextView_maxLength
6194      */
6195     public InputFilter[] getFilters() {
6196         return mFilters;
6197     }
6198
6199     /////////////////////////////////////////////////////////////////////////
6200
6201     private int getBoxHeight(Layout l) {
6202         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
6203         int padding = (l == mHintLayout)
6204                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
6205                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
6206         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
6207     }
6208
6209     int getVerticalOffset(boolean forceNormal) {
6210         int voffset = 0;
6211         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6212
6213         Layout l = mLayout;
6214         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6215             l = mHintLayout;
6216         }
6217
6218         if (gravity != Gravity.TOP) {
6219             int boxht = getBoxHeight(l);
6220             int textht = l.getHeight();
6221
6222             if (textht < boxht) {
6223                 if (gravity == Gravity.BOTTOM) {
6224                     voffset = boxht - textht;
6225                 } else { // (gravity == Gravity.CENTER_VERTICAL)
6226                     voffset = (boxht - textht) >> 1;
6227                 }
6228             }
6229         }
6230         return voffset;
6231     }
6232
6233     private int getBottomVerticalOffset(boolean forceNormal) {
6234         int voffset = 0;
6235         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6236
6237         Layout l = mLayout;
6238         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6239             l = mHintLayout;
6240         }
6241
6242         if (gravity != Gravity.BOTTOM) {
6243             int boxht = getBoxHeight(l);
6244             int textht = l.getHeight();
6245
6246             if (textht < boxht) {
6247                 if (gravity == Gravity.TOP) {
6248                     voffset = boxht - textht;
6249                 } else { // (gravity == Gravity.CENTER_VERTICAL)
6250                     voffset = (boxht - textht) >> 1;
6251                 }
6252             }
6253         }
6254         return voffset;
6255     }
6256
6257     void invalidateCursorPath() {
6258         if (mHighlightPathBogus) {
6259             invalidateCursor();
6260         } else {
6261             final int horizontalPadding = getCompoundPaddingLeft();
6262             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6263
6264             if (mEditor.mCursorCount == 0) {
6265                 synchronized (TEMP_RECTF) {
6266                     /*
6267                      * The reason for this concern about the thickness of the
6268                      * cursor and doing the floor/ceil on the coordinates is that
6269                      * some EditTexts (notably textfields in the Browser) have
6270                      * anti-aliased text where not all the characters are
6271                      * necessarily at integer-multiple locations.  This should
6272                      * make sure the entire cursor gets invalidated instead of
6273                      * sometimes missing half a pixel.
6274                      */
6275                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
6276                     if (thick < 1.0f) {
6277                         thick = 1.0f;
6278                     }
6279
6280                     thick /= 2.0f;
6281
6282                     // mHighlightPath is guaranteed to be non null at that point.
6283                     mHighlightPath.computeBounds(TEMP_RECTF, false);
6284
6285                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
6286                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
6287                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
6288                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
6289                 }
6290             } else {
6291                 for (int i = 0; i < mEditor.mCursorCount; i++) {
6292                     Rect bounds = mEditor.mCursorDrawable[i].getBounds();
6293                     invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
6294                             bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
6295                 }
6296             }
6297         }
6298     }
6299
6300     void invalidateCursor() {
6301         int where = getSelectionEnd();
6302
6303         invalidateCursor(where, where, where);
6304     }
6305
6306     private void invalidateCursor(int a, int b, int c) {
6307         if (a >= 0 || b >= 0 || c >= 0) {
6308             int start = Math.min(Math.min(a, b), c);
6309             int end = Math.max(Math.max(a, b), c);
6310             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
6311         }
6312     }
6313
6314     /**
6315      * Invalidates the region of text enclosed between the start and end text offsets.
6316      */
6317     void invalidateRegion(int start, int end, boolean invalidateCursor) {
6318         if (mLayout == null) {
6319             invalidate();
6320         } else {
6321             int lineStart = mLayout.getLineForOffset(start);
6322             int top = mLayout.getLineTop(lineStart);
6323
6324             // This is ridiculous, but the descent from the line above
6325             // can hang down into the line we really want to redraw,
6326             // so we have to invalidate part of the line above to make
6327             // sure everything that needs to be redrawn really is.
6328             // (But not the whole line above, because that would cause
6329             // the same problem with the descenders on the line above it!)
6330             if (lineStart > 0) {
6331                 top -= mLayout.getLineDescent(lineStart - 1);
6332             }
6333
6334             int lineEnd;
6335
6336             if (start == end) {
6337                 lineEnd = lineStart;
6338             } else {
6339                 lineEnd = mLayout.getLineForOffset(end);
6340             }
6341
6342             int bottom = mLayout.getLineBottom(lineEnd);
6343
6344             // mEditor can be null in case selection is set programmatically.
6345             if (invalidateCursor && mEditor != null) {
6346                 for (int i = 0; i < mEditor.mCursorCount; i++) {
6347                     Rect bounds = mEditor.mCursorDrawable[i].getBounds();
6348                     top = Math.min(top, bounds.top);
6349                     bottom = Math.max(bottom, bounds.bottom);
6350                 }
6351             }
6352
6353             final int compoundPaddingLeft = getCompoundPaddingLeft();
6354             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6355
6356             int left, right;
6357             if (lineStart == lineEnd && !invalidateCursor) {
6358                 left = (int) mLayout.getPrimaryHorizontal(start);
6359                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
6360                 left += compoundPaddingLeft;
6361                 right += compoundPaddingLeft;
6362             } else {
6363                 // Rectangle bounding box when the region spans several lines
6364                 left = compoundPaddingLeft;
6365                 right = getWidth() - getCompoundPaddingRight();
6366             }
6367
6368             invalidate(mScrollX + left, verticalPadding + top,
6369                     mScrollX + right, verticalPadding + bottom);
6370         }
6371     }
6372
6373     private void registerForPreDraw() {
6374         if (!mPreDrawRegistered) {
6375             getViewTreeObserver().addOnPreDrawListener(this);
6376             mPreDrawRegistered = true;
6377         }
6378     }
6379
6380     private void unregisterForPreDraw() {
6381         getViewTreeObserver().removeOnPreDrawListener(this);
6382         mPreDrawRegistered = false;
6383         mPreDrawListenerDetached = false;
6384     }
6385
6386     /**
6387      * {@inheritDoc}
6388      */
6389     @Override
6390     public boolean onPreDraw() {
6391         if (mLayout == null) {
6392             assumeLayout();
6393         }
6394
6395         if (mMovement != null) {
6396             /* This code also provides auto-scrolling when a cursor is moved using a
6397              * CursorController (insertion point or selection limits).
6398              * For selection, ensure start or end is visible depending on controller's state.
6399              */
6400             int curs = getSelectionEnd();
6401             // Do not create the controller if it is not already created.
6402             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
6403                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
6404                 curs = getSelectionStart();
6405             }
6406
6407             /*
6408              * TODO: This should really only keep the end in view if
6409              * it already was before the text changed.  I'm not sure
6410              * of a good way to tell from here if it was.
6411              */
6412             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6413                 curs = mText.length();
6414             }
6415
6416             if (curs >= 0) {
6417                 bringPointIntoView(curs);
6418             }
6419         } else {
6420             bringTextIntoView();
6421         }
6422
6423         // This has to be checked here since:
6424         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
6425         //   a screen rotation) since layout is not yet initialized at that point.
6426         if (mEditor != null && mEditor.mCreatedWithASelection) {
6427             mEditor.refreshTextActionMode();
6428             mEditor.mCreatedWithASelection = false;
6429         }
6430
6431         unregisterForPreDraw();
6432
6433         return true;
6434     }
6435
6436     @Override
6437     protected void onAttachedToWindow() {
6438         super.onAttachedToWindow();
6439
6440         if (mEditor != null) mEditor.onAttachedToWindow();
6441
6442         if (mPreDrawListenerDetached) {
6443             getViewTreeObserver().addOnPreDrawListener(this);
6444             mPreDrawListenerDetached = false;
6445         }
6446     }
6447
6448     /** @hide */
6449     @Override
6450     protected void onDetachedFromWindowInternal() {
6451         if (mPreDrawRegistered) {
6452             getViewTreeObserver().removeOnPreDrawListener(this);
6453             mPreDrawListenerDetached = true;
6454         }
6455
6456         resetResolvedDrawables();
6457
6458         if (mEditor != null) mEditor.onDetachedFromWindow();
6459
6460         super.onDetachedFromWindowInternal();
6461     }
6462
6463     @Override
6464     public void onScreenStateChanged(int screenState) {
6465         super.onScreenStateChanged(screenState);
6466         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
6467     }
6468
6469     @Override
6470     protected boolean isPaddingOffsetRequired() {
6471         return mShadowRadius != 0 || mDrawables != null;
6472     }
6473
6474     @Override
6475     protected int getLeftPaddingOffset() {
6476         return getCompoundPaddingLeft() - mPaddingLeft
6477                 + (int) Math.min(0, mShadowDx - mShadowRadius);
6478     }
6479
6480     @Override
6481     protected int getTopPaddingOffset() {
6482         return (int) Math.min(0, mShadowDy - mShadowRadius);
6483     }
6484
6485     @Override
6486     protected int getBottomPaddingOffset() {
6487         return (int) Math.max(0, mShadowDy + mShadowRadius);
6488     }
6489
6490     @Override
6491     protected int getRightPaddingOffset() {
6492         return -(getCompoundPaddingRight() - mPaddingRight)
6493                 + (int) Math.max(0, mShadowDx + mShadowRadius);
6494     }
6495
6496     @Override
6497     protected boolean verifyDrawable(@NonNull Drawable who) {
6498         final boolean verified = super.verifyDrawable(who);
6499         if (!verified && mDrawables != null) {
6500             for (Drawable dr : mDrawables.mShowing) {
6501                 if (who == dr) {
6502                     return true;
6503                 }
6504             }
6505         }
6506         return verified;
6507     }
6508
6509     @Override
6510     public void jumpDrawablesToCurrentState() {
6511         super.jumpDrawablesToCurrentState();
6512         if (mDrawables != null) {
6513             for (Drawable dr : mDrawables.mShowing) {
6514                 if (dr != null) {
6515                     dr.jumpToCurrentState();
6516                 }
6517             }
6518         }
6519     }
6520
6521     @Override
6522     public void invalidateDrawable(@NonNull Drawable drawable) {
6523         boolean handled = false;
6524
6525         if (verifyDrawable(drawable)) {
6526             final Rect dirty = drawable.getBounds();
6527             int scrollX = mScrollX;
6528             int scrollY = mScrollY;
6529
6530             // IMPORTANT: The coordinates below are based on the coordinates computed
6531             // for each compound drawable in onDraw(). Make sure to update each section
6532             // accordingly.
6533             final TextView.Drawables drawables = mDrawables;
6534             if (drawables != null) {
6535                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
6536                     final int compoundPaddingTop = getCompoundPaddingTop();
6537                     final int compoundPaddingBottom = getCompoundPaddingBottom();
6538                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6539
6540                     scrollX += mPaddingLeft;
6541                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
6542                     handled = true;
6543                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
6544                     final int compoundPaddingTop = getCompoundPaddingTop();
6545                     final int compoundPaddingBottom = getCompoundPaddingBottom();
6546                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6547
6548                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
6549                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
6550                     handled = true;
6551                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
6552                     final int compoundPaddingLeft = getCompoundPaddingLeft();
6553                     final int compoundPaddingRight = getCompoundPaddingRight();
6554                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6555
6556                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
6557                     scrollY += mPaddingTop;
6558                     handled = true;
6559                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
6560                     final int compoundPaddingLeft = getCompoundPaddingLeft();
6561                     final int compoundPaddingRight = getCompoundPaddingRight();
6562                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6563
6564                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
6565                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
6566                     handled = true;
6567                 }
6568             }
6569
6570             if (handled) {
6571                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
6572                         dirty.right + scrollX, dirty.bottom + scrollY);
6573             }
6574         }
6575
6576         if (!handled) {
6577             super.invalidateDrawable(drawable);
6578         }
6579     }
6580
6581     @Override
6582     public boolean hasOverlappingRendering() {
6583         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
6584         return ((getBackground() != null && getBackground().getCurrent() != null)
6585                 || mText instanceof Spannable || hasSelection()
6586                 || isHorizontalFadingEdgeEnabled());
6587     }
6588
6589     /**
6590      *
6591      * Returns the state of the {@code textIsSelectable} flag (See
6592      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
6593      * to allow users to select and copy text in a non-editable TextView, the content of an
6594      * {@link EditText} can always be selected, independently of the value of this flag.
6595      * <p>
6596      *
6597      * @return True if the text displayed in this TextView can be selected by the user.
6598      *
6599      * @attr ref android.R.styleable#TextView_textIsSelectable
6600      */
6601     public boolean isTextSelectable() {
6602         return mEditor == null ? false : mEditor.mTextIsSelectable;
6603     }
6604
6605     /**
6606      * Sets whether the content of this view is selectable by the user. The default is
6607      * {@code false}, meaning that the content is not selectable.
6608      * <p>
6609      * When you use a TextView to display a useful piece of information to the user (such as a
6610      * contact's address), make it selectable, so that the user can select and copy its
6611      * content. You can also use set the XML attribute
6612      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
6613      * <p>
6614      * When you call this method to set the value of {@code textIsSelectable}, it sets
6615      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
6616      * and {@code longClickable} to the same value. These flags correspond to the attributes
6617      * {@link android.R.styleable#View_focusable android:focusable},
6618      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
6619      * {@link android.R.styleable#View_clickable android:clickable}, and
6620      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
6621      * flags to a state you had set previously, call one or more of the following methods:
6622      * {@link #setFocusable(boolean) setFocusable()},
6623      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
6624      * {@link #setClickable(boolean) setClickable()} or
6625      * {@link #setLongClickable(boolean) setLongClickable()}.
6626      *
6627      * @param selectable Whether the content of this TextView should be selectable.
6628      */
6629     public void setTextIsSelectable(boolean selectable) {
6630         if (!selectable && mEditor == null) return; // false is default value with no edit data
6631
6632         createEditorIfNeeded();
6633         if (mEditor.mTextIsSelectable == selectable) return;
6634
6635         mEditor.mTextIsSelectable = selectable;
6636         setFocusableInTouchMode(selectable);
6637         setFocusable(FOCUSABLE_AUTO);
6638         setClickable(selectable);
6639         setLongClickable(selectable);
6640
6641         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
6642
6643         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
6644         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
6645
6646         // Called by setText above, but safer in case of future code changes
6647         mEditor.prepareCursorControllers();
6648     }
6649
6650     @Override
6651     protected int[] onCreateDrawableState(int extraSpace) {
6652         final int[] drawableState;
6653
6654         if (mSingleLine) {
6655             drawableState = super.onCreateDrawableState(extraSpace);
6656         } else {
6657             drawableState = super.onCreateDrawableState(extraSpace + 1);
6658             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
6659         }
6660
6661         if (isTextSelectable()) {
6662             // Disable pressed state, which was introduced when TextView was made clickable.
6663             // Prevents text color change.
6664             // setClickable(false) would have a similar effect, but it also disables focus changes
6665             // and long press actions, which are both needed by text selection.
6666             final int length = drawableState.length;
6667             for (int i = 0; i < length; i++) {
6668                 if (drawableState[i] == R.attr.state_pressed) {
6669                     final int[] nonPressedState = new int[length - 1];
6670                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
6671                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
6672                     return nonPressedState;
6673                 }
6674             }
6675         }
6676
6677         return drawableState;
6678     }
6679
6680     private Path getUpdatedHighlightPath() {
6681         Path highlight = null;
6682         Paint highlightPaint = mHighlightPaint;
6683
6684         final int selStart = getSelectionStart();
6685         final int selEnd = getSelectionEnd();
6686         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
6687             if (selStart == selEnd) {
6688                 if (mEditor != null && mEditor.isCursorVisible()
6689                         && (SystemClock.uptimeMillis() - mEditor.mShowCursor)
6690                         % (2 * Editor.BLINK) < Editor.BLINK) {
6691                     if (mHighlightPathBogus) {
6692                         if (mHighlightPath == null) mHighlightPath = new Path();
6693                         mHighlightPath.reset();
6694                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
6695                         mEditor.updateCursorsPositions();
6696                         mHighlightPathBogus = false;
6697                     }
6698
6699                     // XXX should pass to skin instead of drawing directly
6700                     highlightPaint.setColor(mCurTextColor);
6701                     highlightPaint.setStyle(Paint.Style.STROKE);
6702                     highlight = mHighlightPath;
6703                 }
6704             } else {
6705                 if (mHighlightPathBogus) {
6706                     if (mHighlightPath == null) mHighlightPath = new Path();
6707                     mHighlightPath.reset();
6708                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6709                     mHighlightPathBogus = false;
6710                 }
6711
6712                 // XXX should pass to skin instead of drawing directly
6713                 highlightPaint.setColor(mHighlightColor);
6714                 highlightPaint.setStyle(Paint.Style.FILL);
6715
6716                 highlight = mHighlightPath;
6717             }
6718         }
6719         return highlight;
6720     }
6721
6722     /**
6723      * @hide
6724      */
6725     public int getHorizontalOffsetForDrawables() {
6726         return 0;
6727     }
6728
6729     @Override
6730     protected void onDraw(Canvas canvas) {
6731         restartMarqueeIfNeeded();
6732
6733         // Draw the background for this view
6734         super.onDraw(canvas);
6735
6736         final int compoundPaddingLeft = getCompoundPaddingLeft();
6737         final int compoundPaddingTop = getCompoundPaddingTop();
6738         final int compoundPaddingRight = getCompoundPaddingRight();
6739         final int compoundPaddingBottom = getCompoundPaddingBottom();
6740         final int scrollX = mScrollX;
6741         final int scrollY = mScrollY;
6742         final int right = mRight;
6743         final int left = mLeft;
6744         final int bottom = mBottom;
6745         final int top = mTop;
6746         final boolean isLayoutRtl = isLayoutRtl();
6747         final int offset = getHorizontalOffsetForDrawables();
6748         final int leftOffset = isLayoutRtl ? 0 : offset;
6749         final int rightOffset = isLayoutRtl ? offset : 0;
6750
6751         final Drawables dr = mDrawables;
6752         if (dr != null) {
6753             /*
6754              * Compound, not extended, because the icon is not clipped
6755              * if the text height is smaller.
6756              */
6757
6758             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
6759             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
6760
6761             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6762             // Make sure to update invalidateDrawable() when changing this code.
6763             if (dr.mShowing[Drawables.LEFT] != null) {
6764                 canvas.save();
6765                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
6766                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
6767                 dr.mShowing[Drawables.LEFT].draw(canvas);
6768                 canvas.restore();
6769             }
6770
6771             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6772             // Make sure to update invalidateDrawable() when changing this code.
6773             if (dr.mShowing[Drawables.RIGHT] != null) {
6774                 canvas.save();
6775                 canvas.translate(scrollX + right - left - mPaddingRight
6776                         - dr.mDrawableSizeRight - rightOffset,
6777                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
6778                 dr.mShowing[Drawables.RIGHT].draw(canvas);
6779                 canvas.restore();
6780             }
6781
6782             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6783             // Make sure to update invalidateDrawable() when changing this code.
6784             if (dr.mShowing[Drawables.TOP] != null) {
6785                 canvas.save();
6786                 canvas.translate(scrollX + compoundPaddingLeft
6787                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
6788                 dr.mShowing[Drawables.TOP].draw(canvas);
6789                 canvas.restore();
6790             }
6791
6792             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6793             // Make sure to update invalidateDrawable() when changing this code.
6794             if (dr.mShowing[Drawables.BOTTOM] != null) {
6795                 canvas.save();
6796                 canvas.translate(scrollX + compoundPaddingLeft
6797                         + (hspace - dr.mDrawableWidthBottom) / 2,
6798                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
6799                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
6800                 canvas.restore();
6801             }
6802         }
6803
6804         int color = mCurTextColor;
6805
6806         if (mLayout == null) {
6807             assumeLayout();
6808         }
6809
6810         Layout layout = mLayout;
6811
6812         if (mHint != null && mText.length() == 0) {
6813             if (mHintTextColor != null) {
6814                 color = mCurHintTextColor;
6815             }
6816
6817             layout = mHintLayout;
6818         }
6819
6820         mTextPaint.setColor(color);
6821         mTextPaint.drawableState = getDrawableState();
6822
6823         canvas.save();
6824         /*  Would be faster if we didn't have to do this. Can we chop the
6825             (displayable) text so that we don't need to do this ever?
6826         */
6827
6828         int extendedPaddingTop = getExtendedPaddingTop();
6829         int extendedPaddingBottom = getExtendedPaddingBottom();
6830
6831         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6832         final int maxScrollY = mLayout.getHeight() - vspace;
6833
6834         float clipLeft = compoundPaddingLeft + scrollX;
6835         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
6836         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
6837         float clipBottom = bottom - top + scrollY
6838                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
6839
6840         if (mShadowRadius != 0) {
6841             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
6842             clipRight += Math.max(0, mShadowDx + mShadowRadius);
6843
6844             clipTop += Math.min(0, mShadowDy - mShadowRadius);
6845             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
6846         }
6847
6848         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
6849
6850         int voffsetText = 0;
6851         int voffsetCursor = 0;
6852
6853         // translate in by our padding
6854         /* shortcircuit calling getVerticaOffset() */
6855         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6856             voffsetText = getVerticalOffset(false);
6857             voffsetCursor = getVerticalOffset(true);
6858         }
6859         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
6860
6861         final int layoutDirection = getLayoutDirection();
6862         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
6863         if (isMarqueeFadeEnabled()) {
6864             if (!mSingleLine && getLineCount() == 1 && canMarquee()
6865                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
6866                 final int width = mRight - mLeft;
6867                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
6868                 final float dx = mLayout.getLineRight(0) - (width - padding);
6869                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6870             }
6871
6872             if (mMarquee != null && mMarquee.isRunning()) {
6873                 final float dx = -mMarquee.getScroll();
6874                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6875             }
6876         }
6877
6878         final int cursorOffsetVertical = voffsetCursor - voffsetText;
6879
6880         Path highlight = getUpdatedHighlightPath();
6881         if (mEditor != null) {
6882             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
6883         } else {
6884             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
6885         }
6886
6887         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
6888             final float dx = mMarquee.getGhostOffset();
6889             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6890             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
6891         }
6892
6893         canvas.restore();
6894     }
6895
6896     @Override
6897     public void getFocusedRect(Rect r) {
6898         if (mLayout == null) {
6899             super.getFocusedRect(r);
6900             return;
6901         }
6902
6903         int selEnd = getSelectionEnd();
6904         if (selEnd < 0) {
6905             super.getFocusedRect(r);
6906             return;
6907         }
6908
6909         int selStart = getSelectionStart();
6910         if (selStart < 0 || selStart >= selEnd) {
6911             int line = mLayout.getLineForOffset(selEnd);
6912             r.top = mLayout.getLineTop(line);
6913             r.bottom = mLayout.getLineBottom(line);
6914             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
6915             r.right = r.left + 4;
6916         } else {
6917             int lineStart = mLayout.getLineForOffset(selStart);
6918             int lineEnd = mLayout.getLineForOffset(selEnd);
6919             r.top = mLayout.getLineTop(lineStart);
6920             r.bottom = mLayout.getLineBottom(lineEnd);
6921             if (lineStart == lineEnd) {
6922                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
6923                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
6924             } else {
6925                 // Selection extends across multiple lines -- make the focused
6926                 // rect cover the entire width.
6927                 if (mHighlightPathBogus) {
6928                     if (mHighlightPath == null) mHighlightPath = new Path();
6929                     mHighlightPath.reset();
6930                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6931                     mHighlightPathBogus = false;
6932                 }
6933                 synchronized (TEMP_RECTF) {
6934                     mHighlightPath.computeBounds(TEMP_RECTF, true);
6935                     r.left = (int) TEMP_RECTF.left - 1;
6936                     r.right = (int) TEMP_RECTF.right + 1;
6937                 }
6938             }
6939         }
6940
6941         // Adjust for padding and gravity.
6942         int paddingLeft = getCompoundPaddingLeft();
6943         int paddingTop = getExtendedPaddingTop();
6944         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6945             paddingTop += getVerticalOffset(false);
6946         }
6947         r.offset(paddingLeft, paddingTop);
6948         int paddingBottom = getExtendedPaddingBottom();
6949         r.bottom += paddingBottom;
6950     }
6951
6952     /**
6953      * Return the number of lines of text, or 0 if the internal Layout has not
6954      * been built.
6955      */
6956     public int getLineCount() {
6957         return mLayout != null ? mLayout.getLineCount() : 0;
6958     }
6959
6960     /**
6961      * Return the baseline for the specified line (0...getLineCount() - 1)
6962      * If bounds is not null, return the top, left, right, bottom extents
6963      * of the specified line in it. If the internal Layout has not been built,
6964      * return 0 and set bounds to (0, 0, 0, 0)
6965      * @param line which line to examine (0..getLineCount() - 1)
6966      * @param bounds Optional. If not null, it returns the extent of the line
6967      * @return the Y-coordinate of the baseline
6968      */
6969     public int getLineBounds(int line, Rect bounds) {
6970         if (mLayout == null) {
6971             if (bounds != null) {
6972                 bounds.set(0, 0, 0, 0);
6973             }
6974             return 0;
6975         } else {
6976             int baseline = mLayout.getLineBounds(line, bounds);
6977
6978             int voffset = getExtendedPaddingTop();
6979             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6980                 voffset += getVerticalOffset(true);
6981             }
6982             if (bounds != null) {
6983                 bounds.offset(getCompoundPaddingLeft(), voffset);
6984             }
6985             return baseline + voffset;
6986         }
6987     }
6988
6989     @Override
6990     public int getBaseline() {
6991         if (mLayout == null) {
6992             return super.getBaseline();
6993         }
6994
6995         return getBaselineOffset() + mLayout.getLineBaseline(0);
6996     }
6997
6998     int getBaselineOffset() {
6999         int voffset = 0;
7000         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7001             voffset = getVerticalOffset(true);
7002         }
7003
7004         if (isLayoutModeOptical(mParent)) {
7005             voffset -= getOpticalInsets().top;
7006         }
7007
7008         return getExtendedPaddingTop() + voffset;
7009     }
7010
7011     /**
7012      * @hide
7013      */
7014     @Override
7015     protected int getFadeTop(boolean offsetRequired) {
7016         if (mLayout == null) return 0;
7017
7018         int voffset = 0;
7019         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7020             voffset = getVerticalOffset(true);
7021         }
7022
7023         if (offsetRequired) voffset += getTopPaddingOffset();
7024
7025         return getExtendedPaddingTop() + voffset;
7026     }
7027
7028     /**
7029      * @hide
7030      */
7031     @Override
7032     protected int getFadeHeight(boolean offsetRequired) {
7033         return mLayout != null ? mLayout.getHeight() : 0;
7034     }
7035
7036     @Override
7037     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
7038         if (mText instanceof Spannable && mLinksClickable) {
7039             final float x = event.getX(pointerIndex);
7040             final float y = event.getY(pointerIndex);
7041             final int offset = getOffsetForPosition(x, y);
7042             final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
7043                     ClickableSpan.class);
7044             if (clickables.length > 0) {
7045                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
7046             }
7047         }
7048         if (isTextSelectable() || isTextEditable()) {
7049             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
7050         }
7051         return super.onResolvePointerIcon(event, pointerIndex);
7052     }
7053
7054     @Override
7055     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
7056         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
7057         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
7058         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
7059         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
7060             return true;
7061         }
7062         return super.onKeyPreIme(keyCode, event);
7063     }
7064
7065     /**
7066      * @hide
7067      */
7068     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
7069         // Do nothing unless mEditor is in text action mode.
7070         if (mEditor == null || mEditor.getTextActionMode() == null) {
7071             return false;
7072         }
7073
7074         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
7075             KeyEvent.DispatcherState state = getKeyDispatcherState();
7076             if (state != null) {
7077                 state.startTracking(event, this);
7078             }
7079             return true;
7080         } else if (event.getAction() == KeyEvent.ACTION_UP) {
7081             KeyEvent.DispatcherState state = getKeyDispatcherState();
7082             if (state != null) {
7083                 state.handleUpEvent(event);
7084             }
7085             if (event.isTracking() && !event.isCanceled()) {
7086                 stopTextActionMode();
7087                 return true;
7088             }
7089         }
7090         return false;
7091     }
7092
7093     @Override
7094     public boolean onKeyDown(int keyCode, KeyEvent event) {
7095         final int which = doKeyDown(keyCode, event, null);
7096         if (which == KEY_EVENT_NOT_HANDLED) {
7097             return super.onKeyDown(keyCode, event);
7098         }
7099
7100         return true;
7101     }
7102
7103     @Override
7104     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
7105         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
7106         final int which = doKeyDown(keyCode, down, event);
7107         if (which == KEY_EVENT_NOT_HANDLED) {
7108             // Go through default dispatching.
7109             return super.onKeyMultiple(keyCode, repeatCount, event);
7110         }
7111         if (which == KEY_EVENT_HANDLED) {
7112             // Consumed the whole thing.
7113             return true;
7114         }
7115
7116         repeatCount--;
7117
7118         // We are going to dispatch the remaining events to either the input
7119         // or movement method.  To do this, we will just send a repeated stream
7120         // of down and up events until we have done the complete repeatCount.
7121         // It would be nice if those interfaces had an onKeyMultiple() method,
7122         // but adding that is a more complicated change.
7123         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
7124         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
7125             // mEditor and mEditor.mInput are not null from doKeyDown
7126             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7127             while (--repeatCount > 0) {
7128                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
7129                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7130             }
7131             hideErrorIfUnchanged();
7132
7133         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
7134             // mMovement is not null from doKeyDown
7135             mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
7136             while (--repeatCount > 0) {
7137                 mMovement.onKeyDown(this, (Spannable) mText, keyCode, down);
7138                 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
7139             }
7140         }
7141
7142         return true;
7143     }
7144
7145     /**
7146      * Returns true if pressing ENTER in this field advances focus instead
7147      * of inserting the character.  This is true mostly in single-line fields,
7148      * but also in mail addresses and subjects which will display on multiple
7149      * lines but where it doesn't make sense to insert newlines.
7150      */
7151     private boolean shouldAdvanceFocusOnEnter() {
7152         if (getKeyListener() == null) {
7153             return false;
7154         }
7155
7156         if (mSingleLine) {
7157             return true;
7158         }
7159
7160         if (mEditor != null
7161                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7162                         == EditorInfo.TYPE_CLASS_TEXT) {
7163             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7164             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
7165                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
7166                 return true;
7167             }
7168         }
7169
7170         return false;
7171     }
7172
7173     /**
7174      * Returns true if pressing TAB in this field advances focus instead
7175      * of inserting the character.  Insert tabs only in multi-line editors.
7176      */
7177     private boolean shouldAdvanceFocusOnTab() {
7178         if (getKeyListener() != null && !mSingleLine && mEditor != null
7179                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7180                         == EditorInfo.TYPE_CLASS_TEXT) {
7181             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7182             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
7183                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
7184                 return false;
7185             }
7186         }
7187         return true;
7188     }
7189
7190     private boolean isDirectionalNavigationKey(int keyCode) {
7191         switch(keyCode) {
7192             case KeyEvent.KEYCODE_DPAD_UP:
7193             case KeyEvent.KEYCODE_DPAD_DOWN:
7194             case KeyEvent.KEYCODE_DPAD_LEFT:
7195             case KeyEvent.KEYCODE_DPAD_RIGHT:
7196                 return true;
7197         }
7198         return false;
7199     }
7200
7201     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
7202         if (!isEnabled()) {
7203             return KEY_EVENT_NOT_HANDLED;
7204         }
7205
7206         // If this is the initial keydown, we don't want to prevent a movement away from this view.
7207         // While this shouldn't be necessary because any time we're preventing default movement we
7208         // should be restricting the focus to remain within this view, thus we'll also receive
7209         // the key up event, occasionally key up events will get dropped and we don't want to
7210         // prevent the user from traversing out of this on the next key down.
7211         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7212             mPreventDefaultMovement = false;
7213         }
7214
7215         switch (keyCode) {
7216             case KeyEvent.KEYCODE_ENTER:
7217                 if (event.hasNoModifiers()) {
7218                     // When mInputContentType is set, we know that we are
7219                     // running in a "modern" cupcake environment, so don't need
7220                     // to worry about the application trying to capture
7221                     // enter key events.
7222                     if (mEditor != null && mEditor.mInputContentType != null) {
7223                         // If there is an action listener, given them a
7224                         // chance to consume the event.
7225                         if (mEditor.mInputContentType.onEditorActionListener != null
7226                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7227                                         this, EditorInfo.IME_NULL, event)) {
7228                             mEditor.mInputContentType.enterDown = true;
7229                             // We are consuming the enter key for them.
7230                             return KEY_EVENT_HANDLED;
7231                         }
7232                     }
7233
7234                     // If our editor should move focus when enter is pressed, or
7235                     // this is a generated event from an IME action button, then
7236                     // don't let it be inserted into the text.
7237                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7238                             || shouldAdvanceFocusOnEnter()) {
7239                         if (hasOnClickListeners()) {
7240                             return KEY_EVENT_NOT_HANDLED;
7241                         }
7242                         return KEY_EVENT_HANDLED;
7243                     }
7244                 }
7245                 break;
7246
7247             case KeyEvent.KEYCODE_DPAD_CENTER:
7248                 if (event.hasNoModifiers()) {
7249                     if (shouldAdvanceFocusOnEnter()) {
7250                         return KEY_EVENT_NOT_HANDLED;
7251                     }
7252                 }
7253                 break;
7254
7255             case KeyEvent.KEYCODE_TAB:
7256                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
7257                     if (shouldAdvanceFocusOnTab()) {
7258                         return KEY_EVENT_NOT_HANDLED;
7259                     }
7260                 }
7261                 break;
7262
7263                 // Has to be done on key down (and not on key up) to correctly be intercepted.
7264             case KeyEvent.KEYCODE_BACK:
7265                 if (mEditor != null && mEditor.getTextActionMode() != null) {
7266                     stopTextActionMode();
7267                     return KEY_EVENT_HANDLED;
7268                 }
7269                 break;
7270
7271             case KeyEvent.KEYCODE_CUT:
7272                 if (event.hasNoModifiers() && canCut()) {
7273                     if (onTextContextMenuItem(ID_CUT)) {
7274                         return KEY_EVENT_HANDLED;
7275                     }
7276                 }
7277                 break;
7278
7279             case KeyEvent.KEYCODE_COPY:
7280                 if (event.hasNoModifiers() && canCopy()) {
7281                     if (onTextContextMenuItem(ID_COPY)) {
7282                         return KEY_EVENT_HANDLED;
7283                     }
7284                 }
7285                 break;
7286
7287             case KeyEvent.KEYCODE_PASTE:
7288                 if (event.hasNoModifiers() && canPaste()) {
7289                     if (onTextContextMenuItem(ID_PASTE)) {
7290                         return KEY_EVENT_HANDLED;
7291                     }
7292                 }
7293                 break;
7294         }
7295
7296         if (mEditor != null && mEditor.mKeyListener != null) {
7297             boolean doDown = true;
7298             if (otherEvent != null) {
7299                 try {
7300                     beginBatchEdit();
7301                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
7302                             otherEvent);
7303                     hideErrorIfUnchanged();
7304                     doDown = false;
7305                     if (handled) {
7306                         return KEY_EVENT_HANDLED;
7307                     }
7308                 } catch (AbstractMethodError e) {
7309                     // onKeyOther was added after 1.0, so if it isn't
7310                     // implemented we need to try to dispatch as a regular down.
7311                 } finally {
7312                     endBatchEdit();
7313                 }
7314             }
7315
7316             if (doDown) {
7317                 beginBatchEdit();
7318                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
7319                         keyCode, event);
7320                 endBatchEdit();
7321                 hideErrorIfUnchanged();
7322                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
7323             }
7324         }
7325
7326         // bug 650865: sometimes we get a key event before a layout.
7327         // don't try to move around if we don't know the layout.
7328
7329         if (mMovement != null && mLayout != null) {
7330             boolean doDown = true;
7331             if (otherEvent != null) {
7332                 try {
7333                     boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
7334                             otherEvent);
7335                     doDown = false;
7336                     if (handled) {
7337                         return KEY_EVENT_HANDLED;
7338                     }
7339                 } catch (AbstractMethodError e) {
7340                     // onKeyOther was added after 1.0, so if it isn't
7341                     // implemented we need to try to dispatch as a regular down.
7342                 }
7343             }
7344             if (doDown) {
7345                 if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) {
7346                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7347                         mPreventDefaultMovement = true;
7348                     }
7349                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
7350                 }
7351             }
7352             // Consume arrows from keyboard devices to prevent focus leaving the editor.
7353             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
7354             // to move focus with arrows.
7355             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
7356                     && isDirectionalNavigationKey(keyCode)) {
7357                 return KEY_EVENT_HANDLED;
7358             }
7359         }
7360
7361         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
7362                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
7363     }
7364
7365     /**
7366      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
7367      * can be recorded.
7368      * @hide
7369      */
7370     public void resetErrorChangedFlag() {
7371         /*
7372          * Keep track of what the error was before doing the input
7373          * so that if an input filter changed the error, we leave
7374          * that error showing.  Otherwise, we take down whatever
7375          * error was showing when the user types something.
7376          */
7377         if (mEditor != null) mEditor.mErrorWasChanged = false;
7378     }
7379
7380     /**
7381      * @hide
7382      */
7383     public void hideErrorIfUnchanged() {
7384         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
7385             setError(null, null);
7386         }
7387     }
7388
7389     @Override
7390     public boolean onKeyUp(int keyCode, KeyEvent event) {
7391         if (!isEnabled()) {
7392             return super.onKeyUp(keyCode, event);
7393         }
7394
7395         if (!KeyEvent.isModifierKey(keyCode)) {
7396             mPreventDefaultMovement = false;
7397         }
7398
7399         switch (keyCode) {
7400             case KeyEvent.KEYCODE_DPAD_CENTER:
7401                 if (event.hasNoModifiers()) {
7402                     /*
7403                      * If there is a click listener, just call through to
7404                      * super, which will invoke it.
7405                      *
7406                      * If there isn't a click listener, try to show the soft
7407                      * input method.  (It will also
7408                      * call performClick(), but that won't do anything in
7409                      * this case.)
7410                      */
7411                     if (!hasOnClickListeners()) {
7412                         if (mMovement != null && mText instanceof Editable
7413                                 && mLayout != null && onCheckIsTextEditor()) {
7414                             InputMethodManager imm = InputMethodManager.peekInstance();
7415                             viewClicked(imm);
7416                             if (imm != null && getShowSoftInputOnFocus()) {
7417                                 imm.showSoftInput(this, 0);
7418                             }
7419                         }
7420                     }
7421                 }
7422                 return super.onKeyUp(keyCode, event);
7423
7424             case KeyEvent.KEYCODE_ENTER:
7425                 if (event.hasNoModifiers()) {
7426                     if (mEditor != null && mEditor.mInputContentType != null
7427                             && mEditor.mInputContentType.onEditorActionListener != null
7428                             && mEditor.mInputContentType.enterDown) {
7429                         mEditor.mInputContentType.enterDown = false;
7430                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7431                                 this, EditorInfo.IME_NULL, event)) {
7432                             return true;
7433                         }
7434                     }
7435
7436                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7437                             || shouldAdvanceFocusOnEnter()) {
7438                         /*
7439                          * If there is a click listener, just call through to
7440                          * super, which will invoke it.
7441                          *
7442                          * If there isn't a click listener, try to advance focus,
7443                          * but still call through to super, which will reset the
7444                          * pressed state and longpress state.  (It will also
7445                          * call performClick(), but that won't do anything in
7446                          * this case.)
7447                          */
7448                         if (!hasOnClickListeners()) {
7449                             View v = focusSearch(FOCUS_DOWN);
7450
7451                             if (v != null) {
7452                                 if (!v.requestFocus(FOCUS_DOWN)) {
7453                                     throw new IllegalStateException("focus search returned a view "
7454                                             + "that wasn't able to take focus!");
7455                                 }
7456
7457                                 /*
7458                                  * Return true because we handled the key; super
7459                                  * will return false because there was no click
7460                                  * listener.
7461                                  */
7462                                 super.onKeyUp(keyCode, event);
7463                                 return true;
7464                             } else if ((event.getFlags()
7465                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
7466                                 // No target for next focus, but make sure the IME
7467                                 // if this came from it.
7468                                 InputMethodManager imm = InputMethodManager.peekInstance();
7469                                 if (imm != null && imm.isActive(this)) {
7470                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
7471                                 }
7472                             }
7473                         }
7474                     }
7475                     return super.onKeyUp(keyCode, event);
7476                 }
7477                 break;
7478         }
7479
7480         if (mEditor != null && mEditor.mKeyListener != null) {
7481             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
7482                 return true;
7483             }
7484         }
7485
7486         if (mMovement != null && mLayout != null) {
7487             if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) {
7488                 return true;
7489             }
7490         }
7491
7492         return super.onKeyUp(keyCode, event);
7493     }
7494
7495     @Override
7496     public boolean onCheckIsTextEditor() {
7497         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
7498     }
7499
7500     @Override
7501     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
7502         if (onCheckIsTextEditor() && isEnabled()) {
7503             mEditor.createInputMethodStateIfNeeded();
7504             outAttrs.inputType = getInputType();
7505             if (mEditor.mInputContentType != null) {
7506                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
7507                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
7508                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
7509                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
7510                 outAttrs.extras = mEditor.mInputContentType.extras;
7511                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
7512             } else {
7513                 outAttrs.imeOptions = EditorInfo.IME_NULL;
7514                 outAttrs.hintLocales = null;
7515             }
7516             if (focusSearch(FOCUS_DOWN) != null) {
7517                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
7518             }
7519             if (focusSearch(FOCUS_UP) != null) {
7520                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
7521             }
7522             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
7523                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
7524                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
7525                     // An action has not been set, but the enter key will move to
7526                     // the next focus, so set the action to that.
7527                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
7528                 } else {
7529                     // An action has not been set, and there is no focus to move
7530                     // to, so let's just supply a "done" action.
7531                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
7532                 }
7533                 if (!shouldAdvanceFocusOnEnter()) {
7534                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7535                 }
7536             }
7537             if (isMultilineInputType(outAttrs.inputType)) {
7538                 // Multi-line text editors should always show an enter key.
7539                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7540             }
7541             outAttrs.hintText = mHint;
7542             if (mText instanceof Editable) {
7543                 InputConnection ic = new EditableInputConnection(this);
7544                 outAttrs.initialSelStart = getSelectionStart();
7545                 outAttrs.initialSelEnd = getSelectionEnd();
7546                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
7547                 return ic;
7548             }
7549         }
7550         return null;
7551     }
7552
7553     /**
7554      * If this TextView contains editable content, extract a portion of it
7555      * based on the information in <var>request</var> in to <var>outText</var>.
7556      * @return Returns true if the text was successfully extracted, else false.
7557      */
7558     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
7559         createEditorIfNeeded();
7560         return mEditor.extractText(request, outText);
7561     }
7562
7563     /**
7564      * This is used to remove all style-impacting spans from text before new
7565      * extracted text is being replaced into it, so that we don't have any
7566      * lingering spans applied during the replace.
7567      */
7568     static void removeParcelableSpans(Spannable spannable, int start, int end) {
7569         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
7570         int i = spans.length;
7571         while (i > 0) {
7572             i--;
7573             spannable.removeSpan(spans[i]);
7574         }
7575     }
7576
7577     /**
7578      * Apply to this text view the given extracted text, as previously
7579      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
7580      */
7581     public void setExtractedText(ExtractedText text) {
7582         Editable content = getEditableText();
7583         if (text.text != null) {
7584             if (content == null) {
7585                 setText(text.text, TextView.BufferType.EDITABLE);
7586             } else {
7587                 int start = 0;
7588                 int end = content.length();
7589
7590                 if (text.partialStartOffset >= 0) {
7591                     final int N = content.length();
7592                     start = text.partialStartOffset;
7593                     if (start > N) start = N;
7594                     end = text.partialEndOffset;
7595                     if (end > N) end = N;
7596                 }
7597
7598                 removeParcelableSpans(content, start, end);
7599                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
7600                     if (text.text instanceof Spanned) {
7601                         // OK to copy spans only.
7602                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
7603                                 Object.class, content, start);
7604                     }
7605                 } else {
7606                     content.replace(start, end, text.text);
7607                 }
7608             }
7609         }
7610
7611         // Now set the selection position...  make sure it is in range, to
7612         // avoid crashes.  If this is a partial update, it is possible that
7613         // the underlying text may have changed, causing us problems here.
7614         // Also we just don't want to trust clients to do the right thing.
7615         Spannable sp = (Spannable) getText();
7616         final int N = sp.length();
7617         int start = text.selectionStart;
7618         if (start < 0) {
7619             start = 0;
7620         } else if (start > N) {
7621             start = N;
7622         }
7623         int end = text.selectionEnd;
7624         if (end < 0) {
7625             end = 0;
7626         } else if (end > N) {
7627             end = N;
7628         }
7629         Selection.setSelection(sp, start, end);
7630
7631         // Finally, update the selection mode.
7632         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
7633             MetaKeyKeyListener.startSelecting(this, sp);
7634         } else {
7635             MetaKeyKeyListener.stopSelecting(this, sp);
7636         }
7637     }
7638
7639     /**
7640      * @hide
7641      */
7642     public void setExtracting(ExtractedTextRequest req) {
7643         if (mEditor.mInputMethodState != null) {
7644             mEditor.mInputMethodState.mExtractedTextRequest = req;
7645         }
7646         // This would stop a possible selection mode, but no such mode is started in case
7647         // extracted mode will start. Some text is selected though, and will trigger an action mode
7648         // in the extracted view.
7649         mEditor.hideCursorAndSpanControllers();
7650         stopTextActionMode();
7651         if (mEditor.mSelectionModifierCursorController != null) {
7652             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
7653         }
7654     }
7655
7656     /**
7657      * Called by the framework in response to a text completion from
7658      * the current input method, provided by it calling
7659      * {@link InputConnection#commitCompletion
7660      * InputConnection.commitCompletion()}.  The default implementation does
7661      * nothing; text views that are supporting auto-completion should override
7662      * this to do their desired behavior.
7663      *
7664      * @param text The auto complete text the user has selected.
7665      */
7666     public void onCommitCompletion(CompletionInfo text) {
7667         // intentionally empty
7668     }
7669
7670     /**
7671      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
7672      * dictionary) from the current input method, provided by it calling
7673      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
7674      * The default implementation flashes the background of the corrected word to provide
7675      * feedback to the user.
7676      *
7677      * @param info The auto correct info about the text that was corrected.
7678      */
7679     public void onCommitCorrection(CorrectionInfo info) {
7680         if (mEditor != null) mEditor.onCommitCorrection(info);
7681     }
7682
7683     public void beginBatchEdit() {
7684         if (mEditor != null) mEditor.beginBatchEdit();
7685     }
7686
7687     public void endBatchEdit() {
7688         if (mEditor != null) mEditor.endBatchEdit();
7689     }
7690
7691     /**
7692      * Called by the framework in response to a request to begin a batch
7693      * of edit operations through a call to link {@link #beginBatchEdit()}.
7694      */
7695     public void onBeginBatchEdit() {
7696         // intentionally empty
7697     }
7698
7699     /**
7700      * Called by the framework in response to a request to end a batch
7701      * of edit operations through a call to link {@link #endBatchEdit}.
7702      */
7703     public void onEndBatchEdit() {
7704         // intentionally empty
7705     }
7706
7707     /**
7708      * Called by the framework in response to a private command from the
7709      * current method, provided by it calling
7710      * {@link InputConnection#performPrivateCommand
7711      * InputConnection.performPrivateCommand()}.
7712      *
7713      * @param action The action name of the command.
7714      * @param data Any additional data for the command.  This may be null.
7715      * @return Return true if you handled the command, else false.
7716      */
7717     public boolean onPrivateIMECommand(String action, Bundle data) {
7718         return false;
7719     }
7720
7721     private void nullLayouts() {
7722         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
7723             mSavedLayout = (BoringLayout) mLayout;
7724         }
7725         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
7726             mSavedHintLayout = (BoringLayout) mHintLayout;
7727         }
7728
7729         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
7730
7731         mBoring = mHintBoring = null;
7732
7733         // Since it depends on the value of mLayout
7734         if (mEditor != null) mEditor.prepareCursorControllers();
7735     }
7736
7737     /**
7738      * Make a new Layout based on the already-measured size of the view,
7739      * on the assumption that it was measured correctly at some point.
7740      */
7741     private void assumeLayout() {
7742         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7743
7744         if (width < 1) {
7745             width = 0;
7746         }
7747
7748         int physicalWidth = width;
7749
7750         if (mHorizontallyScrolling) {
7751             width = VERY_WIDE;
7752         }
7753
7754         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
7755                       physicalWidth, false);
7756     }
7757
7758     private Layout.Alignment getLayoutAlignment() {
7759         Layout.Alignment alignment;
7760         switch (getTextAlignment()) {
7761             case TEXT_ALIGNMENT_GRAVITY:
7762                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
7763                     case Gravity.START:
7764                         alignment = Layout.Alignment.ALIGN_NORMAL;
7765                         break;
7766                     case Gravity.END:
7767                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
7768                         break;
7769                     case Gravity.LEFT:
7770                         alignment = Layout.Alignment.ALIGN_LEFT;
7771                         break;
7772                     case Gravity.RIGHT:
7773                         alignment = Layout.Alignment.ALIGN_RIGHT;
7774                         break;
7775                     case Gravity.CENTER_HORIZONTAL:
7776                         alignment = Layout.Alignment.ALIGN_CENTER;
7777                         break;
7778                     default:
7779                         alignment = Layout.Alignment.ALIGN_NORMAL;
7780                         break;
7781                 }
7782                 break;
7783             case TEXT_ALIGNMENT_TEXT_START:
7784                 alignment = Layout.Alignment.ALIGN_NORMAL;
7785                 break;
7786             case TEXT_ALIGNMENT_TEXT_END:
7787                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
7788                 break;
7789             case TEXT_ALIGNMENT_CENTER:
7790                 alignment = Layout.Alignment.ALIGN_CENTER;
7791                 break;
7792             case TEXT_ALIGNMENT_VIEW_START:
7793                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
7794                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
7795                 break;
7796             case TEXT_ALIGNMENT_VIEW_END:
7797                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
7798                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
7799                 break;
7800             case TEXT_ALIGNMENT_INHERIT:
7801                 // This should never happen as we have already resolved the text alignment
7802                 // but better safe than sorry so we just fall through
7803             default:
7804                 alignment = Layout.Alignment.ALIGN_NORMAL;
7805                 break;
7806         }
7807         return alignment;
7808     }
7809
7810     /**
7811      * The width passed in is now the desired layout width,
7812      * not the full view width with padding.
7813      * {@hide}
7814      */
7815     protected void makeNewLayout(int wantWidth, int hintWidth,
7816                                  BoringLayout.Metrics boring,
7817                                  BoringLayout.Metrics hintBoring,
7818                                  int ellipsisWidth, boolean bringIntoView) {
7819         stopMarquee();
7820
7821         // Update "old" cached values
7822         mOldMaximum = mMaximum;
7823         mOldMaxMode = mMaxMode;
7824
7825         mHighlightPathBogus = true;
7826
7827         if (wantWidth < 0) {
7828             wantWidth = 0;
7829         }
7830         if (hintWidth < 0) {
7831             hintWidth = 0;
7832         }
7833
7834         Layout.Alignment alignment = getLayoutAlignment();
7835         final boolean testDirChange = mSingleLine && mLayout != null
7836                 && (alignment == Layout.Alignment.ALIGN_NORMAL
7837                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
7838         int oldDir = 0;
7839         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
7840         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
7841         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
7842                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
7843         TruncateAt effectiveEllipsize = mEllipsize;
7844         if (mEllipsize == TruncateAt.MARQUEE
7845                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7846             effectiveEllipsize = TruncateAt.END_SMALL;
7847         }
7848
7849         if (mTextDir == null) {
7850             mTextDir = getTextDirectionHeuristic();
7851         }
7852
7853         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
7854                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
7855         if (switchEllipsize) {
7856             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
7857                     ? TruncateAt.END : TruncateAt.MARQUEE;
7858             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
7859                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
7860         }
7861
7862         shouldEllipsize = mEllipsize != null;
7863         mHintLayout = null;
7864
7865         if (mHint != null) {
7866             if (shouldEllipsize) hintWidth = wantWidth;
7867
7868             if (hintBoring == UNKNOWN_BORING) {
7869                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
7870                                                    mHintBoring);
7871                 if (hintBoring != null) {
7872                     mHintBoring = hintBoring;
7873                 }
7874             }
7875
7876             if (hintBoring != null) {
7877                 if (hintBoring.width <= hintWidth
7878                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
7879                     if (mSavedHintLayout != null) {
7880                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
7881                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7882                                 hintBoring, mIncludePad);
7883                     } else {
7884                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
7885                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7886                                 hintBoring, mIncludePad);
7887                     }
7888
7889                     mSavedHintLayout = (BoringLayout) mHintLayout;
7890                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
7891                     if (mSavedHintLayout != null) {
7892                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
7893                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7894                                 hintBoring, mIncludePad, mEllipsize,
7895                                 ellipsisWidth);
7896                     } else {
7897                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
7898                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7899                                 hintBoring, mIncludePad, mEllipsize,
7900                                 ellipsisWidth);
7901                     }
7902                 }
7903             }
7904             // TODO: code duplication with makeSingleLayout()
7905             if (mHintLayout == null) {
7906                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
7907                         mHint.length(), mTextPaint, hintWidth)
7908                         .setAlignment(alignment)
7909                         .setTextDirection(mTextDir)
7910                         .setLineSpacing(mSpacingAdd, mSpacingMult)
7911                         .setIncludePad(mIncludePad)
7912                         .setBreakStrategy(mBreakStrategy)
7913                         .setHyphenationFrequency(mHyphenationFrequency)
7914                         .setJustificationMode(mJustificationMode)
7915                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
7916                 if (shouldEllipsize) {
7917                     builder.setEllipsize(mEllipsize)
7918                             .setEllipsizedWidth(ellipsisWidth);
7919                 }
7920                 mHintLayout = builder.build();
7921             }
7922         }
7923
7924         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
7925             registerForPreDraw();
7926         }
7927
7928         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7929             if (!compressText(ellipsisWidth)) {
7930                 final int height = mLayoutParams.height;
7931                 // If the size of the view does not depend on the size of the text, try to
7932                 // start the marquee immediately
7933                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
7934                     startMarquee();
7935                 } else {
7936                     // Defer the start of the marquee until we know our width (see setFrame())
7937                     mRestartMarquee = true;
7938                 }
7939             }
7940         }
7941
7942         // CursorControllers need a non-null mLayout
7943         if (mEditor != null) mEditor.prepareCursorControllers();
7944     }
7945
7946     /**
7947      * @hide
7948      */
7949     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
7950             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
7951             boolean useSaved) {
7952         Layout result = null;
7953         if (mText instanceof Spannable) {
7954             result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
7955                     alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
7956                     mBreakStrategy, mHyphenationFrequency, mJustificationMode,
7957                     getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
7958         } else {
7959             if (boring == UNKNOWN_BORING) {
7960                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7961                 if (boring != null) {
7962                     mBoring = boring;
7963                 }
7964             }
7965
7966             if (boring != null) {
7967                 if (boring.width <= wantWidth
7968                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
7969                     if (useSaved && mSavedLayout != null) {
7970                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7971                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7972                                 boring, mIncludePad);
7973                     } else {
7974                         result = BoringLayout.make(mTransformed, mTextPaint,
7975                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7976                                 boring, mIncludePad);
7977                     }
7978
7979                     if (useSaved) {
7980                         mSavedLayout = (BoringLayout) result;
7981                     }
7982                 } else if (shouldEllipsize && boring.width <= wantWidth) {
7983                     if (useSaved && mSavedLayout != null) {
7984                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7985                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7986                                 boring, mIncludePad, effectiveEllipsize,
7987                                 ellipsisWidth);
7988                     } else {
7989                         result = BoringLayout.make(mTransformed, mTextPaint,
7990                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7991                                 boring, mIncludePad, effectiveEllipsize,
7992                                 ellipsisWidth);
7993                     }
7994                 }
7995             }
7996         }
7997         if (result == null) {
7998             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
7999                     0, mTransformed.length(), mTextPaint, wantWidth)
8000                     .setAlignment(alignment)
8001                     .setTextDirection(mTextDir)
8002                     .setLineSpacing(mSpacingAdd, mSpacingMult)
8003                     .setIncludePad(mIncludePad)
8004                     .setBreakStrategy(mBreakStrategy)
8005                     .setHyphenationFrequency(mHyphenationFrequency)
8006                     .setJustificationMode(mJustificationMode)
8007                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8008             if (shouldEllipsize) {
8009                 builder.setEllipsize(effectiveEllipsize)
8010                         .setEllipsizedWidth(ellipsisWidth);
8011             }
8012             // TODO: explore always setting maxLines
8013             result = builder.build();
8014         }
8015         return result;
8016     }
8017
8018     private boolean compressText(float width) {
8019         if (isHardwareAccelerated()) return false;
8020
8021         // Only compress the text if it hasn't been compressed by the previous pass
8022         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
8023                 && mTextPaint.getTextScaleX() == 1.0f) {
8024             final float textWidth = mLayout.getLineWidth(0);
8025             final float overflow = (textWidth + 1.0f - width) / width;
8026             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
8027                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
8028                 post(new Runnable() {
8029                     public void run() {
8030                         requestLayout();
8031                     }
8032                 });
8033                 return true;
8034             }
8035         }
8036
8037         return false;
8038     }
8039
8040     private static int desired(Layout layout) {
8041         int n = layout.getLineCount();
8042         CharSequence text = layout.getText();
8043         float max = 0;
8044
8045         // if any line was wrapped, we can't use it.
8046         // but it's ok for the last line not to have a newline
8047
8048         for (int i = 0; i < n - 1; i++) {
8049             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
8050                 return -1;
8051             }
8052         }
8053
8054         for (int i = 0; i < n; i++) {
8055             max = Math.max(max, layout.getLineWidth(i));
8056         }
8057
8058         return (int) Math.ceil(max);
8059     }
8060
8061     /**
8062      * Set whether the TextView includes extra top and bottom padding to make
8063      * room for accents that go above the normal ascent and descent.
8064      * The default is true.
8065      *
8066      * @see #getIncludeFontPadding()
8067      *
8068      * @attr ref android.R.styleable#TextView_includeFontPadding
8069      */
8070     public void setIncludeFontPadding(boolean includepad) {
8071         if (mIncludePad != includepad) {
8072             mIncludePad = includepad;
8073
8074             if (mLayout != null) {
8075                 nullLayouts();
8076                 requestLayout();
8077                 invalidate();
8078             }
8079         }
8080     }
8081
8082     /**
8083      * Gets whether the TextView includes extra top and bottom padding to make
8084      * room for accents that go above the normal ascent and descent.
8085      *
8086      * @see #setIncludeFontPadding(boolean)
8087      *
8088      * @attr ref android.R.styleable#TextView_includeFontPadding
8089      */
8090     public boolean getIncludeFontPadding() {
8091         return mIncludePad;
8092     }
8093
8094     private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
8095
8096     @Override
8097     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
8098         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
8099         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
8100         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
8101         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
8102
8103         int width;
8104         int height;
8105
8106         BoringLayout.Metrics boring = UNKNOWN_BORING;
8107         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
8108
8109         if (mTextDir == null) {
8110             mTextDir = getTextDirectionHeuristic();
8111         }
8112
8113         int des = -1;
8114         boolean fromexisting = false;
8115
8116         if (widthMode == MeasureSpec.EXACTLY) {
8117             // Parent has told us how big to be. So be it.
8118             width = widthSize;
8119         } else {
8120             if (mLayout != null && mEllipsize == null) {
8121                 des = desired(mLayout);
8122             }
8123
8124             if (des < 0) {
8125                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8126                 if (boring != null) {
8127                     mBoring = boring;
8128                 }
8129             } else {
8130                 fromexisting = true;
8131             }
8132
8133             if (boring == null || boring == UNKNOWN_BORING) {
8134                 if (des < 0) {
8135                     des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0,
8136                             mTransformed.length(), mTextPaint, mTextDir));
8137                 }
8138                 width = des;
8139             } else {
8140                 width = boring.width;
8141             }
8142
8143             final Drawables dr = mDrawables;
8144             if (dr != null) {
8145                 width = Math.max(width, dr.mDrawableWidthTop);
8146                 width = Math.max(width, dr.mDrawableWidthBottom);
8147             }
8148
8149             if (mHint != null) {
8150                 int hintDes = -1;
8151                 int hintWidth;
8152
8153                 if (mHintLayout != null && mEllipsize == null) {
8154                     hintDes = desired(mHintLayout);
8155                 }
8156
8157                 if (hintDes < 0) {
8158                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
8159                     if (hintBoring != null) {
8160                         mHintBoring = hintBoring;
8161                     }
8162                 }
8163
8164                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
8165                     if (hintDes < 0) {
8166                         hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, 0, mHint.length(),
8167                                 mTextPaint, mTextDir));
8168                     }
8169                     hintWidth = hintDes;
8170                 } else {
8171                     hintWidth = hintBoring.width;
8172                 }
8173
8174                 if (hintWidth > width) {
8175                     width = hintWidth;
8176                 }
8177             }
8178
8179             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
8180
8181             if (mMaxWidthMode == EMS) {
8182                 width = Math.min(width, mMaxWidth * getLineHeight());
8183             } else {
8184                 width = Math.min(width, mMaxWidth);
8185             }
8186
8187             if (mMinWidthMode == EMS) {
8188                 width = Math.max(width, mMinWidth * getLineHeight());
8189             } else {
8190                 width = Math.max(width, mMinWidth);
8191             }
8192
8193             // Check against our minimum width
8194             width = Math.max(width, getSuggestedMinimumWidth());
8195
8196             if (widthMode == MeasureSpec.AT_MOST) {
8197                 width = Math.min(widthSize, width);
8198             }
8199         }
8200
8201         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
8202         int unpaddedWidth = want;
8203
8204         if (mHorizontallyScrolling) want = VERY_WIDE;
8205
8206         int hintWant = want;
8207         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
8208
8209         if (mLayout == null) {
8210             makeNewLayout(want, hintWant, boring, hintBoring,
8211                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8212         } else {
8213             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
8214                     || (mLayout.getEllipsizedWidth()
8215                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
8216
8217             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
8218                     && (want > mLayout.getWidth())
8219                     && (mLayout instanceof BoringLayout
8220                             || (fromexisting && des >= 0 && des <= want));
8221
8222             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
8223
8224             if (layoutChanged || maximumChanged) {
8225                 if (!maximumChanged && widthChanged) {
8226                     mLayout.increaseWidthTo(want);
8227                 } else {
8228                     makeNewLayout(want, hintWant, boring, hintBoring,
8229                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8230                 }
8231             } else {
8232                 // Nothing has changed
8233             }
8234         }
8235
8236         if (heightMode == MeasureSpec.EXACTLY) {
8237             // Parent has told us how big to be. So be it.
8238             height = heightSize;
8239             mDesiredHeightAtMeasure = -1;
8240         } else {
8241             int desired = getDesiredHeight();
8242
8243             height = desired;
8244             mDesiredHeightAtMeasure = desired;
8245
8246             if (heightMode == MeasureSpec.AT_MOST) {
8247                 height = Math.min(desired, heightSize);
8248             }
8249         }
8250
8251         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
8252         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
8253             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
8254         }
8255
8256         /*
8257          * We didn't let makeNewLayout() register to bring the cursor into view,
8258          * so do it here if there is any possibility that it is needed.
8259          */
8260         if (mMovement != null
8261                 || mLayout.getWidth() > unpaddedWidth
8262                 || mLayout.getHeight() > unpaddedHeight) {
8263             registerForPreDraw();
8264         } else {
8265             scrollTo(0, 0);
8266         }
8267
8268         setMeasuredDimension(width, height);
8269     }
8270
8271     /**
8272      * Automatically computes and sets the text size.
8273      */
8274     private void autoSizeText() {
8275         if (!isAutoSizeEnabled()) {
8276             return;
8277         }
8278
8279         if (mNeedsAutoSizeText) {
8280             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
8281                 return;
8282             }
8283
8284             final int availableWidth = mHorizontallyScrolling
8285                     ? VERY_WIDE
8286                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
8287             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
8288                     - getExtendedPaddingTop();
8289
8290             if (availableWidth <= 0 || availableHeight <= 0) {
8291                 return;
8292             }
8293
8294             synchronized (TEMP_RECTF) {
8295                 TEMP_RECTF.setEmpty();
8296                 TEMP_RECTF.right = availableWidth;
8297                 TEMP_RECTF.bottom = availableHeight;
8298                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
8299
8300                 if (optimalTextSize != getTextSize()) {
8301                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
8302                             false /* shouldRequestLayout */);
8303
8304                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
8305                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8306                             false /* bringIntoView */);
8307                 }
8308             }
8309         }
8310         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
8311         // after the next layout pass should set this to false.
8312         mNeedsAutoSizeText = true;
8313     }
8314
8315     /**
8316      * Performs a binary search to find the largest text size that will still fit within the size
8317      * available to this view.
8318      */
8319     private int findLargestTextSizeWhichFits(RectF availableSpace) {
8320         final int sizesCount = mAutoSizeTextSizesInPx.length;
8321         if (sizesCount == 0) {
8322             throw new IllegalStateException("No available text sizes to choose from.");
8323         }
8324
8325         int bestSizeIndex = 0;
8326         int lowIndex = bestSizeIndex + 1;
8327         int highIndex = sizesCount - 1;
8328         int sizeToTryIndex;
8329         while (lowIndex <= highIndex) {
8330             sizeToTryIndex = (lowIndex + highIndex) / 2;
8331             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
8332                 bestSizeIndex = lowIndex;
8333                 lowIndex = sizeToTryIndex + 1;
8334             } else {
8335                 highIndex = sizeToTryIndex - 1;
8336                 bestSizeIndex = highIndex;
8337             }
8338         }
8339
8340         return mAutoSizeTextSizesInPx[bestSizeIndex];
8341     }
8342
8343     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
8344         final CharSequence text = mTransformed != null
8345                 ? mTransformed
8346                 : getText();
8347         final int maxLines = getMaxLines();
8348         if (mTempTextPaint == null) {
8349             mTempTextPaint = new TextPaint();
8350         } else {
8351             mTempTextPaint.reset();
8352         }
8353         mTempTextPaint.set(getPaint());
8354         mTempTextPaint.setTextSize(suggestedSizeInPx);
8355
8356         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
8357                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
8358
8359         layoutBuilder.setAlignment(getLayoutAlignment())
8360                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
8361                 .setIncludePad(getIncludeFontPadding())
8362                 .setBreakStrategy(getBreakStrategy())
8363                 .setHyphenationFrequency(getHyphenationFrequency())
8364                 .setJustificationMode(getJustificationMode())
8365                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
8366                 .setTextDirection(getTextDirectionHeuristic());
8367
8368         final StaticLayout layout = layoutBuilder.build();
8369
8370         // Lines overflow.
8371         if (maxLines != -1 && layout.getLineCount() > maxLines) {
8372             return false;
8373         }
8374
8375         // Height overflow.
8376         if (layout.getHeight() > availableSpace.bottom) {
8377             return false;
8378         }
8379
8380         return true;
8381     }
8382
8383     private int getDesiredHeight() {
8384         return Math.max(
8385                 getDesiredHeight(mLayout, true),
8386                 getDesiredHeight(mHintLayout, mEllipsize != null));
8387     }
8388
8389     private int getDesiredHeight(Layout layout, boolean cap) {
8390         if (layout == null) {
8391             return 0;
8392         }
8393
8394         /*
8395         * Don't cap the hint to a certain number of lines.
8396         * (Do cap it, though, if we have a maximum pixel height.)
8397         */
8398         int desired = layout.getHeight(cap);
8399
8400         final Drawables dr = mDrawables;
8401         if (dr != null) {
8402             desired = Math.max(desired, dr.mDrawableHeightLeft);
8403             desired = Math.max(desired, dr.mDrawableHeightRight);
8404         }
8405
8406         int linecount = layout.getLineCount();
8407         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
8408         desired += padding;
8409
8410         if (mMaxMode != LINES) {
8411             desired = Math.min(desired, mMaximum);
8412         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
8413                 || layout instanceof BoringLayout)) {
8414             desired = layout.getLineTop(mMaximum);
8415
8416             if (dr != null) {
8417                 desired = Math.max(desired, dr.mDrawableHeightLeft);
8418                 desired = Math.max(desired, dr.mDrawableHeightRight);
8419             }
8420
8421             desired += padding;
8422             linecount = mMaximum;
8423         }
8424
8425         if (mMinMode == LINES) {
8426             if (linecount < mMinimum) {
8427                 desired += getLineHeight() * (mMinimum - linecount);
8428             }
8429         } else {
8430             desired = Math.max(desired, mMinimum);
8431         }
8432
8433         // Check against our minimum height
8434         desired = Math.max(desired, getSuggestedMinimumHeight());
8435
8436         return desired;
8437     }
8438
8439     /**
8440      * Check whether a change to the existing text layout requires a
8441      * new view layout.
8442      */
8443     private void checkForResize() {
8444         boolean sizeChanged = false;
8445
8446         if (mLayout != null) {
8447             // Check if our width changed
8448             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
8449                 sizeChanged = true;
8450                 invalidate();
8451             }
8452
8453             // Check if our height changed
8454             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
8455                 int desiredHeight = getDesiredHeight();
8456
8457                 if (desiredHeight != this.getHeight()) {
8458                     sizeChanged = true;
8459                 }
8460             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
8461                 if (mDesiredHeightAtMeasure >= 0) {
8462                     int desiredHeight = getDesiredHeight();
8463
8464                     if (desiredHeight != mDesiredHeightAtMeasure) {
8465                         sizeChanged = true;
8466                     }
8467                 }
8468             }
8469         }
8470
8471         if (sizeChanged) {
8472             requestLayout();
8473             // caller will have already invalidated
8474         }
8475     }
8476
8477     /**
8478      * Check whether entirely new text requires a new view layout
8479      * or merely a new text layout.
8480      */
8481     private void checkForRelayout() {
8482         // If we have a fixed width, we can just swap in a new text layout
8483         // if the text height stays the same or if the view height is fixed.
8484
8485         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
8486                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
8487                 && (mHint == null || mHintLayout != null)
8488                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
8489             // Static width, so try making a new text layout.
8490
8491             int oldht = mLayout.getHeight();
8492             int want = mLayout.getWidth();
8493             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
8494
8495             /*
8496              * No need to bring the text into view, since the size is not
8497              * changing (unless we do the requestLayout(), in which case it
8498              * will happen at measure).
8499              */
8500             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
8501                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8502                           false);
8503
8504             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
8505                 // In a fixed-height view, so use our new text layout.
8506                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
8507                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
8508                     autoSizeText();
8509                     invalidate();
8510                     return;
8511                 }
8512
8513                 // Dynamic height, but height has stayed the same,
8514                 // so use our new text layout.
8515                 if (mLayout.getHeight() == oldht
8516                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
8517                     autoSizeText();
8518                     invalidate();
8519                     return;
8520                 }
8521             }
8522
8523             // We lose: the height has changed and we have a dynamic height.
8524             // Request a new view layout using our new text layout.
8525             requestLayout();
8526             invalidate();
8527         } else {
8528             // Dynamic width, so we have no choice but to request a new
8529             // view layout with a new text layout.
8530             nullLayouts();
8531             requestLayout();
8532             invalidate();
8533         }
8534     }
8535
8536     @Override
8537     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8538         super.onLayout(changed, left, top, right, bottom);
8539         if (mDeferScroll >= 0) {
8540             int curs = mDeferScroll;
8541             mDeferScroll = -1;
8542             bringPointIntoView(Math.min(curs, mText.length()));
8543         }
8544         // Call auto-size after the width and height have been calculated.
8545         autoSizeText();
8546     }
8547
8548     private boolean isShowingHint() {
8549         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
8550     }
8551
8552     /**
8553      * Returns true if anything changed.
8554      */
8555     private boolean bringTextIntoView() {
8556         Layout layout = isShowingHint() ? mHintLayout : mLayout;
8557         int line = 0;
8558         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8559             line = layout.getLineCount() - 1;
8560         }
8561
8562         Layout.Alignment a = layout.getParagraphAlignment(line);
8563         int dir = layout.getParagraphDirection(line);
8564         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8565         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8566         int ht = layout.getHeight();
8567
8568         int scrollx, scrolly;
8569
8570         // Convert to left, center, or right alignment.
8571         if (a == Layout.Alignment.ALIGN_NORMAL) {
8572             a = dir == Layout.DIR_LEFT_TO_RIGHT
8573                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8574         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
8575             a = dir == Layout.DIR_LEFT_TO_RIGHT
8576                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8577         }
8578
8579         if (a == Layout.Alignment.ALIGN_CENTER) {
8580             /*
8581              * Keep centered if possible, or, if it is too wide to fit,
8582              * keep leading edge in view.
8583              */
8584
8585             int left = (int) Math.floor(layout.getLineLeft(line));
8586             int right = (int) Math.ceil(layout.getLineRight(line));
8587
8588             if (right - left < hspace) {
8589                 scrollx = (right + left) / 2 - hspace / 2;
8590             } else {
8591                 if (dir < 0) {
8592                     scrollx = right - hspace;
8593                 } else {
8594                     scrollx = left;
8595                 }
8596             }
8597         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
8598             int right = (int) Math.ceil(layout.getLineRight(line));
8599             scrollx = right - hspace;
8600         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
8601             scrollx = (int) Math.floor(layout.getLineLeft(line));
8602         }
8603
8604         if (ht < vspace) {
8605             scrolly = 0;
8606         } else {
8607             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8608                 scrolly = ht - vspace;
8609             } else {
8610                 scrolly = 0;
8611             }
8612         }
8613
8614         if (scrollx != mScrollX || scrolly != mScrollY) {
8615             scrollTo(scrollx, scrolly);
8616             return true;
8617         } else {
8618             return false;
8619         }
8620     }
8621
8622     /**
8623      * Move the point, specified by the offset, into the view if it is needed.
8624      * This has to be called after layout. Returns true if anything changed.
8625      */
8626     public boolean bringPointIntoView(int offset) {
8627         if (isLayoutRequested()) {
8628             mDeferScroll = offset;
8629             return false;
8630         }
8631         boolean changed = false;
8632
8633         Layout layout = isShowingHint() ? mHintLayout : mLayout;
8634
8635         if (layout == null) return changed;
8636
8637         int line = layout.getLineForOffset(offset);
8638
8639         int grav;
8640
8641         switch (layout.getParagraphAlignment(line)) {
8642             case ALIGN_LEFT:
8643                 grav = 1;
8644                 break;
8645             case ALIGN_RIGHT:
8646                 grav = -1;
8647                 break;
8648             case ALIGN_NORMAL:
8649                 grav = layout.getParagraphDirection(line);
8650                 break;
8651             case ALIGN_OPPOSITE:
8652                 grav = -layout.getParagraphDirection(line);
8653                 break;
8654             case ALIGN_CENTER:
8655             default:
8656                 grav = 0;
8657                 break;
8658         }
8659
8660         // We only want to clamp the cursor to fit within the layout width
8661         // in left-to-right modes, because in a right to left alignment,
8662         // we want to scroll to keep the line-right on the screen, as other
8663         // lines are likely to have text flush with the right margin, which
8664         // we want to keep visible.
8665         // A better long-term solution would probably be to measure both
8666         // the full line and a blank-trimmed version, and, for example, use
8667         // the latter measurement for centering and right alignment, but for
8668         // the time being we only implement the cursor clamping in left to
8669         // right where it is most likely to be annoying.
8670         final boolean clamped = grav > 0;
8671         // FIXME: Is it okay to truncate this, or should we round?
8672         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
8673         final int top = layout.getLineTop(line);
8674         final int bottom = layout.getLineTop(line + 1);
8675
8676         int left = (int) Math.floor(layout.getLineLeft(line));
8677         int right = (int) Math.ceil(layout.getLineRight(line));
8678         int ht = layout.getHeight();
8679
8680         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8681         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8682         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
8683             // If cursor has been clamped, make sure we don't scroll.
8684             right = Math.max(x, left + hspace);
8685         }
8686
8687         int hslack = (bottom - top) / 2;
8688         int vslack = hslack;
8689
8690         if (vslack > vspace / 4) {
8691             vslack = vspace / 4;
8692         }
8693         if (hslack > hspace / 4) {
8694             hslack = hspace / 4;
8695         }
8696
8697         int hs = mScrollX;
8698         int vs = mScrollY;
8699
8700         if (top - vs < vslack) {
8701             vs = top - vslack;
8702         }
8703         if (bottom - vs > vspace - vslack) {
8704             vs = bottom - (vspace - vslack);
8705         }
8706         if (ht - vs < vspace) {
8707             vs = ht - vspace;
8708         }
8709         if (0 - vs > 0) {
8710             vs = 0;
8711         }
8712
8713         if (grav != 0) {
8714             if (x - hs < hslack) {
8715                 hs = x - hslack;
8716             }
8717             if (x - hs > hspace - hslack) {
8718                 hs = x - (hspace - hslack);
8719             }
8720         }
8721
8722         if (grav < 0) {
8723             if (left - hs > 0) {
8724                 hs = left;
8725             }
8726             if (right - hs < hspace) {
8727                 hs = right - hspace;
8728             }
8729         } else if (grav > 0) {
8730             if (right - hs < hspace) {
8731                 hs = right - hspace;
8732             }
8733             if (left - hs > 0) {
8734                 hs = left;
8735             }
8736         } else /* grav == 0 */ {
8737             if (right - left <= hspace) {
8738                 /*
8739                  * If the entire text fits, center it exactly.
8740                  */
8741                 hs = left - (hspace - (right - left)) / 2;
8742             } else if (x > right - hslack) {
8743                 /*
8744                  * If we are near the right edge, keep the right edge
8745                  * at the edge of the view.
8746                  */
8747                 hs = right - hspace;
8748             } else if (x < left + hslack) {
8749                 /*
8750                  * If we are near the left edge, keep the left edge
8751                  * at the edge of the view.
8752                  */
8753                 hs = left;
8754             } else if (left > hs) {
8755                 /*
8756                  * Is there whitespace visible at the left?  Fix it if so.
8757                  */
8758                 hs = left;
8759             } else if (right < hs + hspace) {
8760                 /*
8761                  * Is there whitespace visible at the right?  Fix it if so.
8762                  */
8763                 hs = right - hspace;
8764             } else {
8765                 /*
8766                  * Otherwise, float as needed.
8767                  */
8768                 if (x - hs < hslack) {
8769                     hs = x - hslack;
8770                 }
8771                 if (x - hs > hspace - hslack) {
8772                     hs = x - (hspace - hslack);
8773                 }
8774             }
8775         }
8776
8777         if (hs != mScrollX || vs != mScrollY) {
8778             if (mScroller == null) {
8779                 scrollTo(hs, vs);
8780             } else {
8781                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
8782                 int dx = hs - mScrollX;
8783                 int dy = vs - mScrollY;
8784
8785                 if (duration > ANIMATED_SCROLL_GAP) {
8786                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
8787                     awakenScrollBars(mScroller.getDuration());
8788                     invalidate();
8789                 } else {
8790                     if (!mScroller.isFinished()) {
8791                         mScroller.abortAnimation();
8792                     }
8793
8794                     scrollBy(dx, dy);
8795                 }
8796
8797                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
8798             }
8799
8800             changed = true;
8801         }
8802
8803         if (isFocused()) {
8804             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
8805             // requestRectangleOnScreen() is in terms of content coordinates.
8806
8807             // The offsets here are to ensure the rectangle we are using is
8808             // within our view bounds, in case the cursor is on the far left
8809             // or right.  If it isn't withing the bounds, then this request
8810             // will be ignored.
8811             if (mTempRect == null) mTempRect = new Rect();
8812             mTempRect.set(x - 2, top, x + 2, bottom);
8813             getInterestingRect(mTempRect, line);
8814             mTempRect.offset(mScrollX, mScrollY);
8815
8816             if (requestRectangleOnScreen(mTempRect)) {
8817                 changed = true;
8818             }
8819         }
8820
8821         return changed;
8822     }
8823
8824     /**
8825      * Move the cursor, if needed, so that it is at an offset that is visible
8826      * to the user.  This will not move the cursor if it represents more than
8827      * one character (a selection range).  This will only work if the
8828      * TextView contains spannable text; otherwise it will do nothing.
8829      *
8830      * @return True if the cursor was actually moved, false otherwise.
8831      */
8832     public boolean moveCursorToVisibleOffset() {
8833         if (!(mText instanceof Spannable)) {
8834             return false;
8835         }
8836         int start = getSelectionStart();
8837         int end = getSelectionEnd();
8838         if (start != end) {
8839             return false;
8840         }
8841
8842         // First: make sure the line is visible on screen:
8843
8844         int line = mLayout.getLineForOffset(start);
8845
8846         final int top = mLayout.getLineTop(line);
8847         final int bottom = mLayout.getLineTop(line + 1);
8848         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8849         int vslack = (bottom - top) / 2;
8850         if (vslack > vspace / 4) {
8851             vslack = vspace / 4;
8852         }
8853         final int vs = mScrollY;
8854
8855         if (top < (vs + vslack)) {
8856             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
8857         } else if (bottom > (vspace + vs - vslack)) {
8858             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
8859         }
8860
8861         // Next: make sure the character is visible on screen:
8862
8863         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8864         final int hs = mScrollX;
8865         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
8866         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
8867
8868         // line might contain bidirectional text
8869         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
8870         final int highChar = leftChar > rightChar ? leftChar : rightChar;
8871
8872         int newStart = start;
8873         if (newStart < lowChar) {
8874             newStart = lowChar;
8875         } else if (newStart > highChar) {
8876             newStart = highChar;
8877         }
8878
8879         if (newStart != start) {
8880             Selection.setSelection((Spannable) mText, newStart);
8881             return true;
8882         }
8883
8884         return false;
8885     }
8886
8887     @Override
8888     public void computeScroll() {
8889         if (mScroller != null) {
8890             if (mScroller.computeScrollOffset()) {
8891                 mScrollX = mScroller.getCurrX();
8892                 mScrollY = mScroller.getCurrY();
8893                 invalidateParentCaches();
8894                 postInvalidate();  // So we draw again
8895             }
8896         }
8897     }
8898
8899     private void getInterestingRect(Rect r, int line) {
8900         convertFromViewportToContentCoordinates(r);
8901
8902         // Rectangle can can be expanded on first and last line to take
8903         // padding into account.
8904         // TODO Take left/right padding into account too?
8905         if (line == 0) r.top -= getExtendedPaddingTop();
8906         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
8907     }
8908
8909     private void convertFromViewportToContentCoordinates(Rect r) {
8910         final int horizontalOffset = viewportToContentHorizontalOffset();
8911         r.left += horizontalOffset;
8912         r.right += horizontalOffset;
8913
8914         final int verticalOffset = viewportToContentVerticalOffset();
8915         r.top += verticalOffset;
8916         r.bottom += verticalOffset;
8917     }
8918
8919     int viewportToContentHorizontalOffset() {
8920         return getCompoundPaddingLeft() - mScrollX;
8921     }
8922
8923     int viewportToContentVerticalOffset() {
8924         int offset = getExtendedPaddingTop() - mScrollY;
8925         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8926             offset += getVerticalOffset(false);
8927         }
8928         return offset;
8929     }
8930
8931     @Override
8932     public void debug(int depth) {
8933         super.debug(depth);
8934
8935         String output = debugIndent(depth);
8936         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
8937                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
8938                 + "} ";
8939
8940         if (mText != null) {
8941
8942             output += "mText=\"" + mText + "\" ";
8943             if (mLayout != null) {
8944                 output += "mLayout width=" + mLayout.getWidth()
8945                         + " height=" + mLayout.getHeight();
8946             }
8947         } else {
8948             output += "mText=NULL";
8949         }
8950         Log.d(VIEW_LOG_TAG, output);
8951     }
8952
8953     /**
8954      * Convenience for {@link Selection#getSelectionStart}.
8955      */
8956     @ViewDebug.ExportedProperty(category = "text")
8957     public int getSelectionStart() {
8958         return Selection.getSelectionStart(getText());
8959     }
8960
8961     /**
8962      * Convenience for {@link Selection#getSelectionEnd}.
8963      */
8964     @ViewDebug.ExportedProperty(category = "text")
8965     public int getSelectionEnd() {
8966         return Selection.getSelectionEnd(getText());
8967     }
8968
8969     /**
8970      * Return true iff there is a selection inside this text view.
8971      */
8972     public boolean hasSelection() {
8973         final int selectionStart = getSelectionStart();
8974         final int selectionEnd = getSelectionEnd();
8975
8976         return selectionStart >= 0 && selectionStart != selectionEnd;
8977     }
8978
8979     String getSelectedText() {
8980         if (!hasSelection()) {
8981             return null;
8982         }
8983
8984         final int start = getSelectionStart();
8985         final int end = getSelectionEnd();
8986         return String.valueOf(
8987                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
8988     }
8989
8990     /**
8991      * Sets the properties of this field (lines, horizontally scrolling,
8992      * transformation method) to be for a single-line input.
8993      *
8994      * @attr ref android.R.styleable#TextView_singleLine
8995      */
8996     public void setSingleLine() {
8997         setSingleLine(true);
8998     }
8999
9000     /**
9001      * Sets the properties of this field to transform input to ALL CAPS
9002      * display. This may use a "small caps" formatting if available.
9003      * This setting will be ignored if this field is editable or selectable.
9004      *
9005      * This call replaces the current transformation method. Disabling this
9006      * will not necessarily restore the previous behavior from before this
9007      * was enabled.
9008      *
9009      * @see #setTransformationMethod(TransformationMethod)
9010      * @attr ref android.R.styleable#TextView_textAllCaps
9011      */
9012     public void setAllCaps(boolean allCaps) {
9013         if (allCaps) {
9014             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
9015         } else {
9016             setTransformationMethod(null);
9017         }
9018     }
9019
9020     /**
9021      * If true, sets the properties of this field (number of lines, horizontally scrolling,
9022      * transformation method) to be for a single-line input; if false, restores these to the default
9023      * conditions.
9024      *
9025      * Note that the default conditions are not necessarily those that were in effect prior this
9026      * method, and you may want to reset these properties to your custom values.
9027      *
9028      * @attr ref android.R.styleable#TextView_singleLine
9029      */
9030     @android.view.RemotableViewMethod
9031     public void setSingleLine(boolean singleLine) {
9032         // Could be used, but may break backward compatibility.
9033         // if (mSingleLine == singleLine) return;
9034         setInputTypeSingleLine(singleLine);
9035         applySingleLine(singleLine, true, true);
9036     }
9037
9038     /**
9039      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
9040      * @param singleLine
9041      */
9042     private void setInputTypeSingleLine(boolean singleLine) {
9043         if (mEditor != null
9044                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9045                         == EditorInfo.TYPE_CLASS_TEXT) {
9046             if (singleLine) {
9047                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9048             } else {
9049                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9050             }
9051         }
9052     }
9053
9054     private void applySingleLine(boolean singleLine, boolean applyTransformation,
9055             boolean changeMaxLines) {
9056         mSingleLine = singleLine;
9057         if (singleLine) {
9058             setLines(1);
9059             setHorizontallyScrolling(true);
9060             if (applyTransformation) {
9061                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
9062             }
9063         } else {
9064             if (changeMaxLines) {
9065                 setMaxLines(Integer.MAX_VALUE);
9066             }
9067             setHorizontallyScrolling(false);
9068             if (applyTransformation) {
9069                 setTransformationMethod(null);
9070             }
9071         }
9072     }
9073
9074     /**
9075      * Causes words in the text that are longer than the view's width
9076      * to be ellipsized instead of broken in the middle.  You may also
9077      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
9078      * to constrain the text to a single line.  Use <code>null</code>
9079      * to turn off ellipsizing.
9080      *
9081      * If {@link #setMaxLines} has been used to set two or more lines,
9082      * only {@link android.text.TextUtils.TruncateAt#END} and
9083      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
9084      * (other ellipsizing types will not do anything).
9085      *
9086      * @attr ref android.R.styleable#TextView_ellipsize
9087      */
9088     public void setEllipsize(TextUtils.TruncateAt where) {
9089         // TruncateAt is an enum. != comparison is ok between these singleton objects.
9090         if (mEllipsize != where) {
9091             mEllipsize = where;
9092
9093             if (mLayout != null) {
9094                 nullLayouts();
9095                 requestLayout();
9096                 invalidate();
9097             }
9098         }
9099     }
9100
9101     /**
9102      * Sets how many times to repeat the marquee animation. Only applied if the
9103      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
9104      *
9105      * @see #getMarqueeRepeatLimit()
9106      *
9107      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9108      */
9109     public void setMarqueeRepeatLimit(int marqueeLimit) {
9110         mMarqueeRepeatLimit = marqueeLimit;
9111     }
9112
9113     /**
9114      * Gets the number of times the marquee animation is repeated. Only meaningful if the
9115      * TextView has marquee enabled.
9116      *
9117      * @return the number of times the marquee animation is repeated. -1 if the animation
9118      * repeats indefinitely
9119      *
9120      * @see #setMarqueeRepeatLimit(int)
9121      *
9122      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9123      */
9124     public int getMarqueeRepeatLimit() {
9125         return mMarqueeRepeatLimit;
9126     }
9127
9128     /**
9129      * Returns where, if anywhere, words that are longer than the view
9130      * is wide should be ellipsized.
9131      */
9132     @ViewDebug.ExportedProperty
9133     public TextUtils.TruncateAt getEllipsize() {
9134         return mEllipsize;
9135     }
9136
9137     /**
9138      * Set the TextView so that when it takes focus, all the text is
9139      * selected.
9140      *
9141      * @attr ref android.R.styleable#TextView_selectAllOnFocus
9142      */
9143     @android.view.RemotableViewMethod
9144     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
9145         createEditorIfNeeded();
9146         mEditor.mSelectAllOnFocus = selectAllOnFocus;
9147
9148         if (selectAllOnFocus && !(mText instanceof Spannable)) {
9149             setText(mText, BufferType.SPANNABLE);
9150         }
9151     }
9152
9153     /**
9154      * Set whether the cursor is visible. The default is true. Note that this property only
9155      * makes sense for editable TextView.
9156      *
9157      * @see #isCursorVisible()
9158      *
9159      * @attr ref android.R.styleable#TextView_cursorVisible
9160      */
9161     @android.view.RemotableViewMethod
9162     public void setCursorVisible(boolean visible) {
9163         if (visible && mEditor == null) return; // visible is the default value with no edit data
9164         createEditorIfNeeded();
9165         if (mEditor.mCursorVisible != visible) {
9166             mEditor.mCursorVisible = visible;
9167             invalidate();
9168
9169             mEditor.makeBlink();
9170
9171             // InsertionPointCursorController depends on mCursorVisible
9172             mEditor.prepareCursorControllers();
9173         }
9174     }
9175
9176     /**
9177      * @return whether or not the cursor is visible (assuming this TextView is editable)
9178      *
9179      * @see #setCursorVisible(boolean)
9180      *
9181      * @attr ref android.R.styleable#TextView_cursorVisible
9182      */
9183     public boolean isCursorVisible() {
9184         // true is the default value
9185         return mEditor == null ? true : mEditor.mCursorVisible;
9186     }
9187
9188     private boolean canMarquee() {
9189         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9190         return width > 0 && (mLayout.getLineWidth(0) > width
9191                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
9192                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
9193     }
9194
9195     private void startMarquee() {
9196         // Do not ellipsize EditText
9197         if (getKeyListener() != null) return;
9198
9199         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
9200             return;
9201         }
9202
9203         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
9204                 && getLineCount() == 1 && canMarquee()) {
9205
9206             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9207                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
9208                 final Layout tmp = mLayout;
9209                 mLayout = mSavedMarqueeModeLayout;
9210                 mSavedMarqueeModeLayout = tmp;
9211                 setHorizontalFadingEdgeEnabled(true);
9212                 requestLayout();
9213                 invalidate();
9214             }
9215
9216             if (mMarquee == null) mMarquee = new Marquee(this);
9217             mMarquee.start(mMarqueeRepeatLimit);
9218         }
9219     }
9220
9221     private void stopMarquee() {
9222         if (mMarquee != null && !mMarquee.isStopped()) {
9223             mMarquee.stop();
9224         }
9225
9226         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
9227             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9228             final Layout tmp = mSavedMarqueeModeLayout;
9229             mSavedMarqueeModeLayout = mLayout;
9230             mLayout = tmp;
9231             setHorizontalFadingEdgeEnabled(false);
9232             requestLayout();
9233             invalidate();
9234         }
9235     }
9236
9237     private void startStopMarquee(boolean start) {
9238         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9239             if (start) {
9240                 startMarquee();
9241             } else {
9242                 stopMarquee();
9243             }
9244         }
9245     }
9246
9247     /**
9248      * This method is called when the text is changed, in case any subclasses
9249      * would like to know.
9250      *
9251      * Within <code>text</code>, the <code>lengthAfter</code> characters
9252      * beginning at <code>start</code> have just replaced old text that had
9253      * length <code>lengthBefore</code>. It is an error to attempt to make
9254      * changes to <code>text</code> from this callback.
9255      *
9256      * @param text The text the TextView is displaying
9257      * @param start The offset of the start of the range of the text that was
9258      * modified
9259      * @param lengthBefore The length of the former text that has been replaced
9260      * @param lengthAfter The length of the replacement modified text
9261      */
9262     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
9263         // intentionally empty, template pattern method can be overridden by subclasses
9264     }
9265
9266     /**
9267      * This method is called when the selection has changed, in case any
9268      * subclasses would like to know.
9269      *
9270      * @param selStart The new selection start location.
9271      * @param selEnd The new selection end location.
9272      */
9273     protected void onSelectionChanged(int selStart, int selEnd) {
9274         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
9275     }
9276
9277     /**
9278      * Adds a TextWatcher to the list of those whose methods are called
9279      * whenever this TextView's text changes.
9280      * <p>
9281      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
9282      * not called after {@link #setText} calls.  Now, doing {@link #setText}
9283      * if there are any text changed listeners forces the buffer type to
9284      * Editable if it would not otherwise be and does call this method.
9285      */
9286     public void addTextChangedListener(TextWatcher watcher) {
9287         if (mListeners == null) {
9288             mListeners = new ArrayList<TextWatcher>();
9289         }
9290
9291         mListeners.add(watcher);
9292     }
9293
9294     /**
9295      * Removes the specified TextWatcher from the list of those whose
9296      * methods are called
9297      * whenever this TextView's text changes.
9298      */
9299     public void removeTextChangedListener(TextWatcher watcher) {
9300         if (mListeners != null) {
9301             int i = mListeners.indexOf(watcher);
9302
9303             if (i >= 0) {
9304                 mListeners.remove(i);
9305             }
9306         }
9307     }
9308
9309     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
9310         if (mListeners != null) {
9311             final ArrayList<TextWatcher> list = mListeners;
9312             final int count = list.size();
9313             for (int i = 0; i < count; i++) {
9314                 list.get(i).beforeTextChanged(text, start, before, after);
9315             }
9316         }
9317
9318         // The spans that are inside or intersect the modified region no longer make sense
9319         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
9320         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
9321     }
9322
9323     // Removes all spans that are inside or actually overlap the start..end range
9324     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
9325         if (!(mText instanceof Editable)) return;
9326         Editable text = (Editable) mText;
9327
9328         T[] spans = text.getSpans(start, end, type);
9329         final int length = spans.length;
9330         for (int i = 0; i < length; i++) {
9331             final int spanStart = text.getSpanStart(spans[i]);
9332             final int spanEnd = text.getSpanEnd(spans[i]);
9333             if (spanEnd == start || spanStart == end) break;
9334             text.removeSpan(spans[i]);
9335         }
9336     }
9337
9338     void removeAdjacentSuggestionSpans(final int pos) {
9339         if (!(mText instanceof Editable)) return;
9340         final Editable text = (Editable) mText;
9341
9342         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
9343         final int length = spans.length;
9344         for (int i = 0; i < length; i++) {
9345             final int spanStart = text.getSpanStart(spans[i]);
9346             final int spanEnd = text.getSpanEnd(spans[i]);
9347             if (spanEnd == pos || spanStart == pos) {
9348                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
9349                     text.removeSpan(spans[i]);
9350                 }
9351             }
9352         }
9353     }
9354
9355     /**
9356      * Not private so it can be called from an inner class without going
9357      * through a thunk.
9358      */
9359     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
9360         if (mListeners != null) {
9361             final ArrayList<TextWatcher> list = mListeners;
9362             final int count = list.size();
9363             for (int i = 0; i < count; i++) {
9364                 list.get(i).onTextChanged(text, start, before, after);
9365             }
9366         }
9367
9368         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
9369     }
9370
9371     /**
9372      * Not private so it can be called from an inner class without going
9373      * through a thunk.
9374      */
9375     void sendAfterTextChanged(Editable text) {
9376         if (mListeners != null) {
9377             final ArrayList<TextWatcher> list = mListeners;
9378             final int count = list.size();
9379             for (int i = 0; i < count; i++) {
9380                 list.get(i).afterTextChanged(text);
9381             }
9382         }
9383
9384         // Always notify AutoFillManager - it will return right away if autofill is disabled.
9385         notifyAutoFillManagerAfterTextChangedIfNeeded();
9386
9387         hideErrorIfUnchanged();
9388     }
9389
9390     private void notifyAutoFillManagerAfterTextChangedIfNeeded() {
9391         // It is important to not check whether the view is important for autofill
9392         // since the user can trigger autofill manually on not important views.
9393         if (!isAutofillable()) {
9394             return;
9395         }
9396         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
9397         if (afm != null) {
9398             if (DEBUG_AUTOFILL) {
9399                 Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
9400             }
9401             afm.notifyValueChanged(TextView.this);
9402         }
9403     }
9404
9405     private boolean isAutofillable() {
9406         // It is important to not check whether the view is important for autofill
9407         // since the user can trigger autofill manually on not important views.
9408         return getAutofillType() != AUTOFILL_TYPE_NONE;
9409     }
9410
9411     void updateAfterEdit() {
9412         invalidate();
9413         int curs = getSelectionStart();
9414
9415         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9416             registerForPreDraw();
9417         }
9418
9419         checkForResize();
9420
9421         if (curs >= 0) {
9422             mHighlightPathBogus = true;
9423             if (mEditor != null) mEditor.makeBlink();
9424             bringPointIntoView(curs);
9425         }
9426     }
9427
9428     /**
9429      * Not private so it can be called from an inner class without going
9430      * through a thunk.
9431      */
9432     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
9433         sLastCutCopyOrTextChangedTime = 0;
9434
9435         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9436         if (ims == null || ims.mBatchEditNesting == 0) {
9437             updateAfterEdit();
9438         }
9439         if (ims != null) {
9440             ims.mContentChanged = true;
9441             if (ims.mChangedStart < 0) {
9442                 ims.mChangedStart = start;
9443                 ims.mChangedEnd = start + before;
9444             } else {
9445                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
9446                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
9447             }
9448             ims.mChangedDelta += after - before;
9449         }
9450         resetErrorChangedFlag();
9451         sendOnTextChanged(buffer, start, before, after);
9452         onTextChanged(buffer, start, before, after);
9453     }
9454
9455     /**
9456      * Not private so it can be called from an inner class without going
9457      * through a thunk.
9458      */
9459     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
9460         // XXX Make the start and end move together if this ends up
9461         // spending too much time invalidating.
9462
9463         boolean selChanged = false;
9464         int newSelStart = -1, newSelEnd = -1;
9465
9466         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9467
9468         if (what == Selection.SELECTION_END) {
9469             selChanged = true;
9470             newSelEnd = newStart;
9471
9472             if (oldStart >= 0 || newStart >= 0) {
9473                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
9474                 checkForResize();
9475                 registerForPreDraw();
9476                 if (mEditor != null) mEditor.makeBlink();
9477             }
9478         }
9479
9480         if (what == Selection.SELECTION_START) {
9481             selChanged = true;
9482             newSelStart = newStart;
9483
9484             if (oldStart >= 0 || newStart >= 0) {
9485                 int end = Selection.getSelectionEnd(buf);
9486                 invalidateCursor(end, oldStart, newStart);
9487             }
9488         }
9489
9490         if (selChanged) {
9491             mHighlightPathBogus = true;
9492             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
9493
9494             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
9495                 if (newSelStart < 0) {
9496                     newSelStart = Selection.getSelectionStart(buf);
9497                 }
9498                 if (newSelEnd < 0) {
9499                     newSelEnd = Selection.getSelectionEnd(buf);
9500                 }
9501
9502                 if (mEditor != null) {
9503                     mEditor.refreshTextActionMode();
9504                     if (!hasSelection()
9505                             && mEditor.getTextActionMode() == null && hasTransientState()) {
9506                         // User generated selection has been removed.
9507                         setHasTransientState(false);
9508                     }
9509                 }
9510                 onSelectionChanged(newSelStart, newSelEnd);
9511             }
9512         }
9513
9514         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
9515                 || what instanceof CharacterStyle) {
9516             if (ims == null || ims.mBatchEditNesting == 0) {
9517                 invalidate();
9518                 mHighlightPathBogus = true;
9519                 checkForResize();
9520             } else {
9521                 ims.mContentChanged = true;
9522             }
9523             if (mEditor != null) {
9524                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
9525                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
9526                 mEditor.invalidateHandlesAndActionMode();
9527             }
9528         }
9529
9530         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
9531             mHighlightPathBogus = true;
9532             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
9533                 ims.mSelectionModeChanged = true;
9534             }
9535
9536             if (Selection.getSelectionStart(buf) >= 0) {
9537                 if (ims == null || ims.mBatchEditNesting == 0) {
9538                     invalidateCursor();
9539                 } else {
9540                     ims.mCursorChanged = true;
9541                 }
9542             }
9543         }
9544
9545         if (what instanceof ParcelableSpan) {
9546             // If this is a span that can be sent to a remote process,
9547             // the current extract editor would be interested in it.
9548             if (ims != null && ims.mExtractedTextRequest != null) {
9549                 if (ims.mBatchEditNesting != 0) {
9550                     if (oldStart >= 0) {
9551                         if (ims.mChangedStart > oldStart) {
9552                             ims.mChangedStart = oldStart;
9553                         }
9554                         if (ims.mChangedStart > oldEnd) {
9555                             ims.mChangedStart = oldEnd;
9556                         }
9557                     }
9558                     if (newStart >= 0) {
9559                         if (ims.mChangedStart > newStart) {
9560                             ims.mChangedStart = newStart;
9561                         }
9562                         if (ims.mChangedStart > newEnd) {
9563                             ims.mChangedStart = newEnd;
9564                         }
9565                     }
9566                 } else {
9567                     if (DEBUG_EXTRACT) {
9568                         Log.v(LOG_TAG, "Span change outside of batch: "
9569                                 + oldStart + "-" + oldEnd + ","
9570                                 + newStart + "-" + newEnd + " " + what);
9571                     }
9572                     ims.mContentChanged = true;
9573                 }
9574             }
9575         }
9576
9577         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
9578                 && what instanceof SpellCheckSpan) {
9579             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
9580         }
9581     }
9582
9583     @Override
9584     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
9585         if (isTemporarilyDetached()) {
9586             // If we are temporarily in the detach state, then do nothing.
9587             super.onFocusChanged(focused, direction, previouslyFocusedRect);
9588             return;
9589         }
9590
9591         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
9592
9593         if (focused) {
9594             if (mText instanceof Spannable) {
9595                 Spannable sp = (Spannable) mText;
9596                 MetaKeyKeyListener.resetMetaState(sp);
9597             }
9598         }
9599
9600         startStopMarquee(focused);
9601
9602         if (mTransformation != null) {
9603             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
9604         }
9605
9606         super.onFocusChanged(focused, direction, previouslyFocusedRect);
9607     }
9608
9609     @Override
9610     public void onWindowFocusChanged(boolean hasWindowFocus) {
9611         super.onWindowFocusChanged(hasWindowFocus);
9612
9613         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
9614
9615         startStopMarquee(hasWindowFocus);
9616     }
9617
9618     @Override
9619     protected void onVisibilityChanged(View changedView, int visibility) {
9620         super.onVisibilityChanged(changedView, visibility);
9621         if (mEditor != null && visibility != VISIBLE) {
9622             mEditor.hideCursorAndSpanControllers();
9623             stopTextActionMode();
9624         }
9625     }
9626
9627     /**
9628      * Use {@link BaseInputConnection#removeComposingSpans
9629      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
9630      * state from this text view.
9631      */
9632     public void clearComposingText() {
9633         if (mText instanceof Spannable) {
9634             BaseInputConnection.removeComposingSpans((Spannable) mText);
9635         }
9636     }
9637
9638     @Override
9639     public void setSelected(boolean selected) {
9640         boolean wasSelected = isSelected();
9641
9642         super.setSelected(selected);
9643
9644         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9645             if (selected) {
9646                 startMarquee();
9647             } else {
9648                 stopMarquee();
9649             }
9650         }
9651     }
9652
9653     @Override
9654     public boolean onTouchEvent(MotionEvent event) {
9655         final int action = event.getActionMasked();
9656         if (mEditor != null) {
9657             mEditor.onTouchEvent(event);
9658
9659             if (mEditor.mSelectionModifierCursorController != null
9660                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
9661                 return true;
9662             }
9663         }
9664
9665         final boolean superResult = super.onTouchEvent(event);
9666
9667         /*
9668          * Don't handle the release after a long press, because it will move the selection away from
9669          * whatever the menu action was trying to affect. If the long press should have triggered an
9670          * insertion action mode, we can now actually show it.
9671          */
9672         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
9673             mEditor.mDiscardNextActionUp = false;
9674
9675             if (mEditor.mIsInsertionActionModeStartPending) {
9676                 mEditor.startInsertionActionMode();
9677                 mEditor.mIsInsertionActionModeStartPending = false;
9678             }
9679             return superResult;
9680         }
9681
9682         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
9683                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
9684
9685         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
9686                 && mText instanceof Spannable && mLayout != null) {
9687             boolean handled = false;
9688
9689             if (mMovement != null) {
9690                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
9691             }
9692
9693             final boolean textIsSelectable = isTextSelectable();
9694             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
9695                 // The LinkMovementMethod which should handle taps on links has not been installed
9696                 // on non editable text that support text selection.
9697                 // We reproduce its behavior here to open links for these.
9698                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
9699                     getSelectionEnd(), ClickableSpan.class);
9700
9701                 if (links.length > 0) {
9702                     links[0].onClick(this);
9703                     handled = true;
9704                 }
9705             }
9706
9707             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
9708                 // Show the IME, except when selecting in read-only text.
9709                 final InputMethodManager imm = InputMethodManager.peekInstance();
9710                 viewClicked(imm);
9711                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9712                     imm.showSoftInput(this, 0);
9713                 }
9714
9715                 // The above condition ensures that the mEditor is not null
9716                 mEditor.onTouchUpEvent(event);
9717
9718                 handled = true;
9719             }
9720
9721             if (handled) {
9722                 return true;
9723             }
9724         }
9725
9726         return superResult;
9727     }
9728
9729     @Override
9730     public boolean onGenericMotionEvent(MotionEvent event) {
9731         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9732             try {
9733                 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
9734                     return true;
9735                 }
9736             } catch (AbstractMethodError ex) {
9737                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
9738                 // Ignore its absence in case third party applications implemented the
9739                 // interface directly.
9740             }
9741         }
9742         return super.onGenericMotionEvent(event);
9743     }
9744
9745     @Override
9746     protected void onCreateContextMenu(ContextMenu menu) {
9747         if (mEditor != null) {
9748             mEditor.onCreateContextMenu(menu);
9749         }
9750     }
9751
9752     @Override
9753     public boolean showContextMenu() {
9754         if (mEditor != null) {
9755             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
9756         }
9757         return super.showContextMenu();
9758     }
9759
9760     @Override
9761     public boolean showContextMenu(float x, float y) {
9762         if (mEditor != null) {
9763             mEditor.setContextMenuAnchor(x, y);
9764         }
9765         return super.showContextMenu(x, y);
9766     }
9767
9768     /**
9769      * @return True iff this TextView contains a text that can be edited, or if this is
9770      * a selectable TextView.
9771      */
9772     boolean isTextEditable() {
9773         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
9774     }
9775
9776     /**
9777      * Returns true, only while processing a touch gesture, if the initial
9778      * touch down event caused focus to move to the text view and as a result
9779      * its selection changed.  Only valid while processing the touch gesture
9780      * of interest, in an editable text view.
9781      */
9782     public boolean didTouchFocusSelect() {
9783         return mEditor != null && mEditor.mTouchFocusSelected;
9784     }
9785
9786     @Override
9787     public void cancelLongPress() {
9788         super.cancelLongPress();
9789         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
9790     }
9791
9792     @Override
9793     public boolean onTrackballEvent(MotionEvent event) {
9794         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9795             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
9796                 return true;
9797             }
9798         }
9799
9800         return super.onTrackballEvent(event);
9801     }
9802
9803     /**
9804      * Sets the Scroller used for producing a scrolling animation
9805      *
9806      * @param s A Scroller instance
9807      */
9808     public void setScroller(Scroller s) {
9809         mScroller = s;
9810     }
9811
9812     @Override
9813     protected float getLeftFadingEdgeStrength() {
9814         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9815             final Marquee marquee = mMarquee;
9816             if (marquee.shouldDrawLeftFade()) {
9817                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
9818             } else {
9819                 return 0.0f;
9820             }
9821         } else if (getLineCount() == 1) {
9822             final float lineLeft = getLayout().getLineLeft(0);
9823             if (lineLeft > mScrollX) return 0.0f;
9824             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
9825         }
9826         return super.getLeftFadingEdgeStrength();
9827     }
9828
9829     @Override
9830     protected float getRightFadingEdgeStrength() {
9831         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9832             final Marquee marquee = mMarquee;
9833             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
9834         } else if (getLineCount() == 1) {
9835             final float rightEdge = mScrollX +
9836                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
9837             final float lineRight = getLayout().getLineRight(0);
9838             if (lineRight < rightEdge) return 0.0f;
9839             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
9840         }
9841         return super.getRightFadingEdgeStrength();
9842     }
9843
9844     /**
9845      * Calculates the fading edge strength as the ratio of the distance between two
9846      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
9847      * value for the distance calculation.
9848      *
9849      * @param position1 A horizontal position.
9850      * @param position2 A horizontal position.
9851      * @return Fading edge strength between [0.0f, 1.0f].
9852      */
9853     @FloatRange(from = 0.0, to = 1.0)
9854     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
9855         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
9856         if (horizontalFadingEdgeLength == 0) return 0.0f;
9857         final float diff = Math.abs(position1 - position2);
9858         if (diff > horizontalFadingEdgeLength) return 1.0f;
9859         return diff / horizontalFadingEdgeLength;
9860     }
9861
9862     private boolean isMarqueeFadeEnabled() {
9863         return mEllipsize == TextUtils.TruncateAt.MARQUEE
9864                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9865     }
9866
9867     @Override
9868     protected int computeHorizontalScrollRange() {
9869         if (mLayout != null) {
9870             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
9871                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
9872         }
9873
9874         return super.computeHorizontalScrollRange();
9875     }
9876
9877     @Override
9878     protected int computeVerticalScrollRange() {
9879         if (mLayout != null) {
9880             return mLayout.getHeight();
9881         }
9882         return super.computeVerticalScrollRange();
9883     }
9884
9885     @Override
9886     protected int computeVerticalScrollExtent() {
9887         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
9888     }
9889
9890     @Override
9891     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
9892         super.findViewsWithText(outViews, searched, flags);
9893         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
9894                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
9895             String searchedLowerCase = searched.toString().toLowerCase();
9896             String textLowerCase = mText.toString().toLowerCase();
9897             if (textLowerCase.contains(searchedLowerCase)) {
9898                 outViews.add(this);
9899             }
9900         }
9901     }
9902
9903     /**
9904      * Type of the text buffer that defines the characteristics of the text such as static,
9905      * styleable, or editable.
9906      */
9907     public enum BufferType {
9908         NORMAL, SPANNABLE, EDITABLE
9909     }
9910
9911     /**
9912      * Returns the TextView_textColor attribute from the TypedArray, if set, or
9913      * the TextAppearance_textColor from the TextView_textAppearance attribute,
9914      * if TextView_textColor was not set directly.
9915      *
9916      * @removed
9917      */
9918     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
9919         if (attrs == null) {
9920             // Preserve behavior prior to removal of this API.
9921             throw new NullPointerException();
9922         }
9923
9924         // It's not safe to use this method from apps. The parameter 'attrs'
9925         // must have been obtained using the TextView filter array which is not
9926         // available to the SDK. As such, we grab a default TypedArray with the
9927         // right filter instead here.
9928         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
9929         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
9930         if (colors == null) {
9931             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
9932             if (ap != 0) {
9933                 final TypedArray appearance = context.obtainStyledAttributes(
9934                         ap, R.styleable.TextAppearance);
9935                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
9936                 appearance.recycle();
9937             }
9938         }
9939         a.recycle();
9940
9941         return colors;
9942     }
9943
9944     /**
9945      * Returns the default color from the TextView_textColor attribute from the
9946      * AttributeSet, if set, or the default color from the
9947      * TextAppearance_textColor from the TextView_textAppearance attribute, if
9948      * TextView_textColor was not set directly.
9949      *
9950      * @removed
9951      */
9952     public static int getTextColor(Context context, TypedArray attrs, int def) {
9953         final ColorStateList colors = getTextColors(context, attrs);
9954         if (colors == null) {
9955             return def;
9956         } else {
9957             return colors.getDefaultColor();
9958         }
9959     }
9960
9961     @Override
9962     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
9963         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
9964             // Handle Ctrl-only shortcuts.
9965             switch (keyCode) {
9966                 case KeyEvent.KEYCODE_A:
9967                     if (canSelectText()) {
9968                         return onTextContextMenuItem(ID_SELECT_ALL);
9969                     }
9970                     break;
9971                 case KeyEvent.KEYCODE_Z:
9972                     if (canUndo()) {
9973                         return onTextContextMenuItem(ID_UNDO);
9974                     }
9975                     break;
9976                 case KeyEvent.KEYCODE_X:
9977                     if (canCut()) {
9978                         return onTextContextMenuItem(ID_CUT);
9979                     }
9980                     break;
9981                 case KeyEvent.KEYCODE_C:
9982                     if (canCopy()) {
9983                         return onTextContextMenuItem(ID_COPY);
9984                     }
9985                     break;
9986                 case KeyEvent.KEYCODE_V:
9987                     if (canPaste()) {
9988                         return onTextContextMenuItem(ID_PASTE);
9989                     }
9990                     break;
9991             }
9992         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
9993             // Handle Ctrl-Shift shortcuts.
9994             switch (keyCode) {
9995                 case KeyEvent.KEYCODE_Z:
9996                     if (canRedo()) {
9997                         return onTextContextMenuItem(ID_REDO);
9998                     }
9999                     break;
10000                 case KeyEvent.KEYCODE_V:
10001                     if (canPaste()) {
10002                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
10003                     }
10004             }
10005         }
10006         return super.onKeyShortcut(keyCode, event);
10007     }
10008
10009     /**
10010      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
10011      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
10012      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
10013      * sufficient.
10014      */
10015     boolean canSelectText() {
10016         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
10017     }
10018
10019     /**
10020      * Test based on the <i>intrinsic</i> charateristics of the TextView.
10021      * The text must be spannable and the movement method must allow for arbitary selection.
10022      *
10023      * See also {@link #canSelectText()}.
10024      */
10025     boolean textCanBeSelected() {
10026         // prepareCursorController() relies on this method.
10027         // If you change this condition, make sure prepareCursorController is called anywhere
10028         // the value of this condition might be changed.
10029         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
10030         return isTextEditable()
10031                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
10032     }
10033
10034     private Locale getTextServicesLocale(boolean allowNullLocale) {
10035         // Start fetching the text services locale asynchronously.
10036         updateTextServicesLocaleAsync();
10037         // If !allowNullLocale and there is no cached text services locale, just return the default
10038         // locale.
10039         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
10040                 : mCurrentSpellCheckerLocaleCache;
10041     }
10042
10043     /**
10044      * This is a temporary method. Future versions may support multi-locale text.
10045      * Caveat: This method may not return the latest text services locale, but this should be
10046      * acceptable and it's more important to make this method asynchronous.
10047      *
10048      * @return The locale that should be used for a word iterator
10049      * in this TextView, based on the current spell checker settings,
10050      * the current IME's locale, or the system default locale.
10051      * Please note that a word iterator in this TextView is different from another word iterator
10052      * used by SpellChecker.java of TextView. This method should be used for the former.
10053      * @hide
10054      */
10055     // TODO: Support multi-locale
10056     // TODO: Update the text services locale immediately after the keyboard locale is switched
10057     // by catching intent of keyboard switch event
10058     public Locale getTextServicesLocale() {
10059         return getTextServicesLocale(false /* allowNullLocale */);
10060     }
10061
10062     /**
10063      * @return {@code true} if this TextView is specialized for showing and interacting with the
10064      * extracted text in a full-screen input method.
10065      * @hide
10066      */
10067     public boolean isInExtractedMode() {
10068         return false;
10069     }
10070
10071     /**
10072      * @return {@code true} if this widget supports auto-sizing text and has been configured to
10073      * auto-size.
10074      */
10075     private boolean isAutoSizeEnabled() {
10076         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
10077     }
10078
10079     /**
10080      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
10081      * @hide
10082      */
10083     protected boolean supportsAutoSizeText() {
10084         return true;
10085     }
10086
10087     /**
10088      * This is a temporary method. Future versions may support multi-locale text.
10089      * Caveat: This method may not return the latest spell checker locale, but this should be
10090      * acceptable and it's more important to make this method asynchronous.
10091      *
10092      * @return The locale that should be used for a spell checker in this TextView,
10093      * based on the current spell checker settings, the current IME's locale, or the system default
10094      * locale.
10095      * @hide
10096      */
10097     public Locale getSpellCheckerLocale() {
10098         return getTextServicesLocale(true /* allowNullLocale */);
10099     }
10100
10101     private void updateTextServicesLocaleAsync() {
10102         // AsyncTask.execute() uses a serial executor which means we don't have
10103         // to lock around updateTextServicesLocaleLocked() to prevent it from
10104         // being executed n times in parallel.
10105         AsyncTask.execute(new Runnable() {
10106             @Override
10107             public void run() {
10108                 updateTextServicesLocaleLocked();
10109             }
10110         });
10111     }
10112
10113     private void updateTextServicesLocaleLocked() {
10114         final TextServicesManager textServicesManager = (TextServicesManager)
10115                 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
10116         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
10117         final Locale locale;
10118         if (subtype != null) {
10119             locale = subtype.getLocaleObject();
10120         } else {
10121             locale = null;
10122         }
10123         mCurrentSpellCheckerLocaleCache = locale;
10124     }
10125
10126     void onLocaleChanged() {
10127         mEditor.onLocaleChanged();
10128     }
10129
10130     /**
10131      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
10132      * Made available to achieve a consistent behavior.
10133      * @hide
10134      */
10135     public WordIterator getWordIterator() {
10136         if (mEditor != null) {
10137             return mEditor.getWordIterator();
10138         } else {
10139             return null;
10140         }
10141     }
10142
10143     /** @hide */
10144     @Override
10145     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
10146         super.onPopulateAccessibilityEventInternal(event);
10147
10148         final CharSequence text = getTextForAccessibility();
10149         if (!TextUtils.isEmpty(text)) {
10150             event.getText().add(text);
10151         }
10152     }
10153
10154     @Override
10155     public CharSequence getAccessibilityClassName() {
10156         return TextView.class.getName();
10157     }
10158
10159     @Override
10160     public void onProvideStructure(ViewStructure structure) {
10161         super.onProvideStructure(structure);
10162         onProvideAutoStructureForAssistOrAutofill(structure, false);
10163     }
10164
10165     @Override
10166     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
10167         super.onProvideAutofillStructure(structure, flags);
10168         onProvideAutoStructureForAssistOrAutofill(structure, true);
10169     }
10170
10171     private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure,
10172             boolean forAutofill) {
10173         final boolean isPassword = hasPasswordTransformationMethod()
10174                 || isPasswordInputType(getInputType());
10175         if (forAutofill) {
10176             structure.setDataIsSensitive(!mTextFromResource);
10177         }
10178
10179         if (!isPassword || forAutofill) {
10180             if (mLayout == null) {
10181                 assumeLayout();
10182             }
10183             Layout layout = mLayout;
10184             final int lineCount = layout.getLineCount();
10185             if (lineCount <= 1) {
10186                 // Simple case: this is a single line.
10187                 final CharSequence text = getText();
10188                 if (forAutofill) {
10189                     structure.setText(text);
10190                 } else {
10191                     structure.setText(text, getSelectionStart(), getSelectionEnd());
10192                 }
10193             } else {
10194                 // Complex case: multi-line, could be scrolled or within a scroll container
10195                 // so some lines are not visible.
10196                 final int[] tmpCords = new int[2];
10197                 getLocationInWindow(tmpCords);
10198                 final int topWindowLocation = tmpCords[1];
10199                 View root = this;
10200                 ViewParent viewParent = getParent();
10201                 while (viewParent instanceof View) {
10202                     root = (View) viewParent;
10203                     viewParent = root.getParent();
10204                 }
10205                 final int windowHeight = root.getHeight();
10206                 final int topLine;
10207                 final int bottomLine;
10208                 if (topWindowLocation >= 0) {
10209                     // The top of the view is fully within its window; start text at line 0.
10210                     topLine = getLineAtCoordinateUnclamped(0);
10211                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
10212                 } else {
10213                     // The top of hte window has scrolled off the top of the window; figure out
10214                     // the starting line for this.
10215                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
10216                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
10217                 }
10218                 // We want to return some contextual lines above/below the lines that are
10219                 // actually visible.
10220                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
10221                 if (expandedTopLine < 0) {
10222                     expandedTopLine = 0;
10223                 }
10224                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
10225                 if (expandedBottomLine >= lineCount) {
10226                     expandedBottomLine = lineCount - 1;
10227                 }
10228
10229                 // Convert lines into character offsets.
10230                 int expandedTopChar = layout.getLineStart(expandedTopLine);
10231                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
10232
10233                 // Take into account selection -- if there is a selection, we need to expand
10234                 // the text we are returning to include that selection.
10235                 final int selStart = getSelectionStart();
10236                 final int selEnd = getSelectionEnd();
10237                 if (selStart < selEnd) {
10238                     if (selStart < expandedTopChar) {
10239                         expandedTopChar = selStart;
10240                     }
10241                     if (selEnd > expandedBottomChar) {
10242                         expandedBottomChar = selEnd;
10243                     }
10244                 }
10245
10246                 // Get the text and trim it to the range we are reporting.
10247                 CharSequence text = getText();
10248                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
10249                     text = text.subSequence(expandedTopChar, expandedBottomChar);
10250                 }
10251
10252                 if (forAutofill) {
10253                     structure.setText(text);
10254                 } else {
10255                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
10256
10257                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
10258                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
10259                     final int baselineOffset = getBaselineOffset();
10260                     for (int i = topLine; i <= bottomLine; i++) {
10261                         lineOffsets[i - topLine] = layout.getLineStart(i);
10262                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
10263                     }
10264                     structure.setTextLines(lineOffsets, lineBaselines);
10265                 }
10266             }
10267
10268             if (!forAutofill) {
10269                 // Extract style information that applies to the TextView as a whole.
10270                 int style = 0;
10271                 int typefaceStyle = getTypefaceStyle();
10272                 if ((typefaceStyle & Typeface.BOLD) != 0) {
10273                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10274                 }
10275                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
10276                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
10277                 }
10278
10279                 // Global styles can also be set via TextView.setPaintFlags().
10280                 int paintFlags = mTextPaint.getFlags();
10281                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
10282                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10283                 }
10284                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
10285                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
10286                 }
10287                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
10288                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
10289                 }
10290
10291                 // TextView does not have its own text background color. A background is either part
10292                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
10293                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
10294                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
10295             }
10296         }
10297         structure.setHint(getHint());
10298         structure.setInputType(getInputType());
10299     }
10300
10301     boolean canRequestAutofill() {
10302         if (!isAutofillable()) {
10303             return false;
10304         }
10305         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10306         if (afm != null) {
10307             return afm.isEnabled();
10308         }
10309         return false;
10310     }
10311
10312     private void requestAutofill() {
10313         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10314         if (afm != null) {
10315             afm.requestAutofill(this);
10316         }
10317     }
10318
10319     @Override
10320     public void autofill(AutofillValue value) {
10321         if (!value.isText() || !isTextEditable()) {
10322             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
10323             return;
10324         }
10325
10326         final CharSequence autofilledValue = value.getTextValue();
10327
10328         // First autofill it...
10329         setText(autofilledValue, mBufferType, true, 0);
10330
10331         // ...then move cursor to the end.
10332         final CharSequence text = getText();
10333         if ((text instanceof Spannable)) {
10334             Selection.setSelection((Spannable) text, text.length());
10335         }
10336     }
10337
10338     @Override
10339     public @AutofillType int getAutofillType() {
10340         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
10341     }
10342
10343     @Override
10344     @Nullable
10345     public AutofillValue getAutofillValue() {
10346         return isTextEditable() ? AutofillValue.forText(getText()) : null;
10347     }
10348
10349     /** @hide */
10350     @Override
10351     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
10352         super.onInitializeAccessibilityEventInternal(event);
10353
10354         final boolean isPassword = hasPasswordTransformationMethod();
10355         event.setPassword(isPassword);
10356
10357         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
10358             event.setFromIndex(Selection.getSelectionStart(mText));
10359             event.setToIndex(Selection.getSelectionEnd(mText));
10360             event.setItemCount(mText.length());
10361         }
10362     }
10363
10364     /** @hide */
10365     @Override
10366     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
10367         super.onInitializeAccessibilityNodeInfoInternal(info);
10368
10369         final boolean isPassword = hasPasswordTransformationMethod();
10370         info.setPassword(isPassword);
10371         info.setText(getTextForAccessibility());
10372         info.setHintText(mHint);
10373         info.setShowingHintText(isShowingHint());
10374
10375         if (mBufferType == BufferType.EDITABLE) {
10376             info.setEditable(true);
10377             if (isEnabled()) {
10378                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
10379             }
10380         }
10381
10382         if (mEditor != null) {
10383             info.setInputType(mEditor.mInputType);
10384
10385             if (mEditor.mError != null) {
10386                 info.setContentInvalid(true);
10387                 info.setError(mEditor.mError);
10388             }
10389         }
10390
10391         if (!TextUtils.isEmpty(mText)) {
10392             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
10393             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
10394             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
10395                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
10396                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
10397                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
10398                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
10399             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
10400             info.setAvailableExtraData(
10401                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
10402         }
10403
10404         if (isFocused()) {
10405             if (canCopy()) {
10406                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
10407             }
10408             if (canPaste()) {
10409                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
10410             }
10411             if (canCut()) {
10412                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
10413             }
10414             if (canShare()) {
10415                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
10416                         ACCESSIBILITY_ACTION_SHARE,
10417                         getResources().getString(com.android.internal.R.string.share)));
10418             }
10419             if (canProcessText()) {  // also implies mEditor is not null.
10420                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
10421             }
10422         }
10423
10424         // Check for known input filter types.
10425         final int numFilters = mFilters.length;
10426         for (int i = 0; i < numFilters; i++) {
10427             final InputFilter filter = mFilters[i];
10428             if (filter instanceof InputFilter.LengthFilter) {
10429                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
10430             }
10431         }
10432
10433         if (!isSingleLine()) {
10434             info.setMultiLine(true);
10435         }
10436     }
10437
10438     @Override
10439     public void addExtraDataToAccessibilityNodeInfo(
10440             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
10441         // The only extra data we support requires arguments.
10442         if (arguments == null) {
10443             return;
10444         }
10445         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
10446             int positionInfoStartIndex = arguments.getInt(
10447                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
10448             int positionInfoLength = arguments.getInt(
10449                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
10450             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
10451                     || (positionInfoStartIndex >= mText.length())) {
10452                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
10453                 return;
10454             }
10455             RectF[] boundingRects = new RectF[positionInfoLength];
10456             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
10457             populateCharacterBounds(builder, positionInfoStartIndex,
10458                     positionInfoStartIndex + positionInfoLength,
10459                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
10460             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
10461             for (int i = 0; i < positionInfoLength; i++) {
10462                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
10463                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
10464                     RectF bounds = cursorAnchorInfo
10465                             .getCharacterBounds(positionInfoStartIndex + i);
10466                     if (bounds != null) {
10467                         mapRectFromViewToScreenCoords(bounds, true);
10468                         boundingRects[i] = bounds;
10469                     }
10470                 }
10471             }
10472             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
10473         }
10474     }
10475
10476     /**
10477      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
10478      *
10479      * @param builder The builder to populate
10480      * @param startIndex The starting character index to populate
10481      * @param endIndex The ending character index to populate
10482      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
10483      * content
10484      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
10485      * @hide
10486      */
10487     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
10488             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
10489             float viewportToContentVerticalOffset) {
10490         final int minLine = mLayout.getLineForOffset(startIndex);
10491         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
10492         for (int line = minLine; line <= maxLine; ++line) {
10493             final int lineStart = mLayout.getLineStart(line);
10494             final int lineEnd = mLayout.getLineEnd(line);
10495             final int offsetStart = Math.max(lineStart, startIndex);
10496             final int offsetEnd = Math.min(lineEnd, endIndex);
10497             final boolean ltrLine =
10498                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
10499             final float[] widths = new float[offsetEnd - offsetStart];
10500             mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths);
10501             final float top = mLayout.getLineTop(line);
10502             final float bottom = mLayout.getLineBottom(line);
10503             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
10504                 final float charWidth = widths[offset - offsetStart];
10505                 final boolean isRtl = mLayout.isRtlCharAt(offset);
10506                 final float primary = mLayout.getPrimaryHorizontal(offset);
10507                 final float secondary = mLayout.getSecondaryHorizontal(offset);
10508                 // TODO: This doesn't work perfectly for text with custom styles and
10509                 // TAB chars.
10510                 final float left;
10511                 final float right;
10512                 if (ltrLine) {
10513                     if (isRtl) {
10514                         left = secondary - charWidth;
10515                         right = secondary;
10516                     } else {
10517                         left = primary;
10518                         right = primary + charWidth;
10519                     }
10520                 } else {
10521                     if (!isRtl) {
10522                         left = secondary;
10523                         right = secondary + charWidth;
10524                     } else {
10525                         left = primary - charWidth;
10526                         right = primary;
10527                     }
10528                 }
10529                 // TODO: Check top-right and bottom-left as well.
10530                 final float localLeft = left + viewportToContentHorizontalOffset;
10531                 final float localRight = right + viewportToContentHorizontalOffset;
10532                 final float localTop = top + viewportToContentVerticalOffset;
10533                 final float localBottom = bottom + viewportToContentVerticalOffset;
10534                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
10535                 final boolean isBottomRightVisible =
10536                         isPositionVisible(localRight, localBottom);
10537                 int characterBoundsFlags = 0;
10538                 if (isTopLeftVisible || isBottomRightVisible) {
10539                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
10540                 }
10541                 if (!isTopLeftVisible || !isBottomRightVisible) {
10542                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
10543                 }
10544                 if (isRtl) {
10545                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
10546                 }
10547                 // Here offset is the index in Java chars.
10548                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
10549                         localBottom, characterBoundsFlags);
10550             }
10551         }
10552     }
10553
10554     /**
10555      * @hide
10556      */
10557     public boolean isPositionVisible(final float positionX, final float positionY) {
10558         synchronized (TEMP_POSITION) {
10559             final float[] position = TEMP_POSITION;
10560             position[0] = positionX;
10561             position[1] = positionY;
10562             View view = this;
10563
10564             while (view != null) {
10565                 if (view != this) {
10566                     // Local scroll is already taken into account in positionX/Y
10567                     position[0] -= view.getScrollX();
10568                     position[1] -= view.getScrollY();
10569                 }
10570
10571                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
10572                         || position[1] > view.getHeight()) {
10573                     return false;
10574                 }
10575
10576                 if (!view.getMatrix().isIdentity()) {
10577                     view.getMatrix().mapPoints(position);
10578                 }
10579
10580                 position[0] += view.getLeft();
10581                 position[1] += view.getTop();
10582
10583                 final ViewParent parent = view.getParent();
10584                 if (parent instanceof View) {
10585                     view = (View) parent;
10586                 } else {
10587                     // We've reached the ViewRoot, stop iterating
10588                     view = null;
10589                 }
10590             }
10591         }
10592
10593         // We've been able to walk up the view hierarchy and the position was never clipped
10594         return true;
10595     }
10596
10597     /**
10598      * Performs an accessibility action after it has been offered to the
10599      * delegate.
10600      *
10601      * @hide
10602      */
10603     @Override
10604     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
10605         if (mEditor != null
10606                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
10607             return true;
10608         }
10609         switch (action) {
10610             case AccessibilityNodeInfo.ACTION_CLICK: {
10611                 return performAccessibilityActionClick(arguments);
10612             }
10613             case AccessibilityNodeInfo.ACTION_COPY: {
10614                 if (isFocused() && canCopy()) {
10615                     if (onTextContextMenuItem(ID_COPY)) {
10616                         return true;
10617                     }
10618                 }
10619             } return false;
10620             case AccessibilityNodeInfo.ACTION_PASTE: {
10621                 if (isFocused() && canPaste()) {
10622                     if (onTextContextMenuItem(ID_PASTE)) {
10623                         return true;
10624                     }
10625                 }
10626             } return false;
10627             case AccessibilityNodeInfo.ACTION_CUT: {
10628                 if (isFocused() && canCut()) {
10629                     if (onTextContextMenuItem(ID_CUT)) {
10630                         return true;
10631                     }
10632                 }
10633             } return false;
10634             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
10635                 ensureIterableTextForAccessibilitySelectable();
10636                 CharSequence text = getIterableTextForAccessibility();
10637                 if (text == null) {
10638                     return false;
10639                 }
10640                 final int start = (arguments != null) ? arguments.getInt(
10641                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
10642                 final int end = (arguments != null) ? arguments.getInt(
10643                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
10644                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
10645                     // No arguments clears the selection.
10646                     if (start == end && end == -1) {
10647                         Selection.removeSelection((Spannable) text);
10648                         return true;
10649                     }
10650                     if (start >= 0 && start <= end && end <= text.length()) {
10651                         Selection.setSelection((Spannable) text, start, end);
10652                         // Make sure selection mode is engaged.
10653                         if (mEditor != null) {
10654                             mEditor.startSelectionActionModeAsync(false);
10655                         }
10656                         return true;
10657                     }
10658                 }
10659             } return false;
10660             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
10661             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
10662                 ensureIterableTextForAccessibilitySelectable();
10663                 return super.performAccessibilityActionInternal(action, arguments);
10664             }
10665             case ACCESSIBILITY_ACTION_SHARE: {
10666                 if (isFocused() && canShare()) {
10667                     if (onTextContextMenuItem(ID_SHARE)) {
10668                         return true;
10669                     }
10670                 }
10671             } return false;
10672             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
10673                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
10674                     return false;
10675                 }
10676                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
10677                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
10678                 setText(text);
10679                 if (mText != null) {
10680                     int updatedTextLength = mText.length();
10681                     if (updatedTextLength > 0) {
10682                         Selection.setSelection((Spannable) mText, updatedTextLength);
10683                     }
10684                 }
10685             } return true;
10686             default: {
10687                 return super.performAccessibilityActionInternal(action, arguments);
10688             }
10689         }
10690     }
10691
10692     private boolean performAccessibilityActionClick(Bundle arguments) {
10693         boolean handled = false;
10694
10695         if (!isEnabled()) {
10696             return false;
10697         }
10698
10699         if (isClickable() || isLongClickable()) {
10700             // Simulate View.onTouchEvent for an ACTION_UP event
10701             if (isFocusable() && !isFocused()) {
10702                 requestFocus();
10703             }
10704
10705             performClick();
10706             handled = true;
10707         }
10708
10709         // Show the IME, except when selecting in read-only text.
10710         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
10711                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
10712             final InputMethodManager imm = InputMethodManager.peekInstance();
10713             viewClicked(imm);
10714             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10715                 handled |= imm.showSoftInput(this, 0);
10716             }
10717         }
10718
10719         return handled;
10720     }
10721
10722     private boolean hasSpannableText() {
10723         return mText != null && mText instanceof Spannable;
10724     }
10725
10726     /** @hide */
10727     @Override
10728     public void sendAccessibilityEventInternal(int eventType) {
10729         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
10730             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
10731         }
10732
10733         // Do not send scroll events since first they are not interesting for
10734         // accessibility and second such events a generated too frequently.
10735         // For details see the implementation of bringTextIntoView().
10736         if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
10737             return;
10738         }
10739         super.sendAccessibilityEventInternal(eventType);
10740     }
10741
10742     /**
10743      * Returns the text that should be exposed to accessibility services.
10744      * <p>
10745      * This approximates what is displayed visually. If the user has specified
10746      * that accessibility services should speak passwords, this method will
10747      * bypass any password transformation method and return unobscured text.
10748      *
10749      * @return the text that should be exposed to accessibility services, may
10750      *         be {@code null} if no text is set
10751      */
10752     @Nullable
10753     private CharSequence getTextForAccessibility() {
10754         // If the text is empty, we must be showing the hint text.
10755         if (TextUtils.isEmpty(mText)) {
10756             return mHint;
10757         }
10758
10759         // Otherwise, return whatever text is being displayed.
10760         return mTransformed;
10761     }
10762
10763     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
10764             int fromIndex, int removedCount, int addedCount) {
10765         AccessibilityEvent event =
10766                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
10767         event.setFromIndex(fromIndex);
10768         event.setRemovedCount(removedCount);
10769         event.setAddedCount(addedCount);
10770         event.setBeforeText(beforeText);
10771         sendAccessibilityEventUnchecked(event);
10772     }
10773
10774     /**
10775      * Returns whether this text view is a current input method target.  The
10776      * default implementation just checks with {@link InputMethodManager}.
10777      * @return True if the TextView is a current input method target; false otherwise.
10778      */
10779     public boolean isInputMethodTarget() {
10780         InputMethodManager imm = InputMethodManager.peekInstance();
10781         return imm != null && imm.isActive(this);
10782     }
10783
10784     static final int ID_SELECT_ALL = android.R.id.selectAll;
10785     static final int ID_UNDO = android.R.id.undo;
10786     static final int ID_REDO = android.R.id.redo;
10787     static final int ID_CUT = android.R.id.cut;
10788     static final int ID_COPY = android.R.id.copy;
10789     static final int ID_PASTE = android.R.id.paste;
10790     static final int ID_SHARE = android.R.id.shareText;
10791     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
10792     static final int ID_REPLACE = android.R.id.replaceText;
10793     static final int ID_ASSIST = android.R.id.textAssist;
10794     static final int ID_AUTOFILL = android.R.id.autofill;
10795
10796     /**
10797      * Called when a context menu option for the text view is selected.  Currently
10798      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
10799      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
10800      *
10801      * @return true if the context menu item action was performed.
10802      */
10803     public boolean onTextContextMenuItem(int id) {
10804         int min = 0;
10805         int max = mText.length();
10806
10807         if (isFocused()) {
10808             final int selStart = getSelectionStart();
10809             final int selEnd = getSelectionEnd();
10810
10811             min = Math.max(0, Math.min(selStart, selEnd));
10812             max = Math.max(0, Math.max(selStart, selEnd));
10813         }
10814
10815         switch (id) {
10816             case ID_SELECT_ALL:
10817                 final boolean hadSelection = hasSelection();
10818                 selectAllText();
10819                 if (mEditor != null && hadSelection) {
10820                     mEditor.invalidateActionModeAsync();
10821                 }
10822                 return true;
10823
10824             case ID_UNDO:
10825                 if (mEditor != null) {
10826                     mEditor.undo();
10827                 }
10828                 return true;  // Returns true even if nothing was undone.
10829
10830             case ID_REDO:
10831                 if (mEditor != null) {
10832                     mEditor.redo();
10833                 }
10834                 return true;  // Returns true even if nothing was undone.
10835
10836             case ID_PASTE:
10837                 paste(min, max, true /* withFormatting */);
10838                 return true;
10839
10840             case ID_PASTE_AS_PLAIN_TEXT:
10841                 paste(min, max, false /* withFormatting */);
10842                 return true;
10843
10844             case ID_CUT:
10845                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
10846                 deleteText_internal(min, max);
10847                 return true;
10848
10849             case ID_COPY:
10850                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
10851                 stopTextActionMode();
10852                 return true;
10853
10854             case ID_REPLACE:
10855                 if (mEditor != null) {
10856                     mEditor.replace();
10857                 }
10858                 return true;
10859
10860             case ID_SHARE:
10861                 shareSelectedText();
10862                 return true;
10863
10864             case ID_AUTOFILL:
10865                 requestAutofill();
10866                 stopTextActionMode();
10867                 return true;
10868         }
10869         return false;
10870     }
10871
10872     CharSequence getTransformedText(int start, int end) {
10873         return removeSuggestionSpans(mTransformed.subSequence(start, end));
10874     }
10875
10876     @Override
10877     public boolean performLongClick() {
10878         boolean handled = false;
10879         boolean performedHapticFeedback = false;
10880
10881         if (mEditor != null) {
10882             mEditor.mIsBeingLongClicked = true;
10883         }
10884
10885         if (super.performLongClick()) {
10886             handled = true;
10887             performedHapticFeedback = true;
10888         }
10889
10890         if (mEditor != null) {
10891             handled |= mEditor.performLongClick(handled);
10892             mEditor.mIsBeingLongClicked = false;
10893         }
10894
10895         if (handled) {
10896             if (!performedHapticFeedback) {
10897               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
10898             }
10899             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
10900         } else {
10901             MetricsLogger.action(
10902                     mContext,
10903                     MetricsEvent.TEXT_LONGPRESS,
10904                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
10905         }
10906
10907         return handled;
10908     }
10909
10910     @Override
10911     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
10912         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
10913         if (mEditor != null) {
10914             mEditor.onScrollChanged();
10915         }
10916     }
10917
10918     /**
10919      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
10920      * by the IME or by the spell checker as the user types. This is done by adding
10921      * {@link SuggestionSpan}s to the text.
10922      *
10923      * When suggestions are enabled (default), this list of suggestions will be displayed when the
10924      * user asks for them on these parts of the text. This value depends on the inputType of this
10925      * TextView.
10926      *
10927      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
10928      *
10929      * In addition, the type variation must be one of
10930      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
10931      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
10932      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
10933      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
10934      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
10935      *
10936      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
10937      *
10938      * @return true if the suggestions popup window is enabled, based on the inputType.
10939      */
10940     public boolean isSuggestionsEnabled() {
10941         if (mEditor == null) return false;
10942         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
10943             return false;
10944         }
10945         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
10946
10947         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
10948         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
10949                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
10950                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
10951                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
10952                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
10953     }
10954
10955     /**
10956      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10957      * selection is initiated in this View.
10958      *
10959      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
10960      * Paste, Replace and Share actions, depending on what this View supports.
10961      *
10962      * <p>A custom implementation can add new entries in the default menu in its
10963      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
10964      * method. The default actions can also be removed from the menu using
10965      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
10966      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
10967      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
10968      *
10969      * <p>Returning false from
10970      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
10971      * will prevent the action mode from being started.
10972      *
10973      * <p>Action click events should be handled by the custom implementation of
10974      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
10975      * android.view.MenuItem)}.
10976      *
10977      * <p>Note that text selection mode is not started when a TextView receives focus and the
10978      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
10979      * that case, to allow for quick replacement.
10980      */
10981     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
10982         createEditorIfNeeded();
10983         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
10984     }
10985
10986     /**
10987      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
10988      *
10989      * @return The current custom selection callback.
10990      */
10991     public ActionMode.Callback getCustomSelectionActionModeCallback() {
10992         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
10993     }
10994
10995     /**
10996      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10997      * insertion is initiated in this View.
10998      * The standard implementation populates the menu with a subset of Select All,
10999      * Paste and Replace actions, depending on what this View supports.
11000      *
11001      * <p>A custom implementation can add new entries in the default menu in its
11002      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
11003      * android.view.Menu)} method. The default actions can also be removed from the menu using
11004      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11005      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
11006      *
11007      * <p>Returning false from
11008      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
11009      * android.view.Menu)} will prevent the action mode from being started.</p>
11010      *
11011      * <p>Action click events should be handled by the custom implementation of
11012      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
11013      * android.view.MenuItem)}.</p>
11014      *
11015      * <p>Note that text insertion mode is not started when a TextView receives focus and the
11016      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
11017      */
11018     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
11019         createEditorIfNeeded();
11020         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
11021     }
11022
11023     /**
11024      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
11025      *
11026      * @return The current custom insertion callback.
11027      */
11028     public ActionMode.Callback getCustomInsertionActionModeCallback() {
11029         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
11030     }
11031
11032     /**
11033      * Sets the {@link TextClassifier} for this TextView.
11034      */
11035     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
11036         mTextClassifier = textClassifier;
11037     }
11038
11039     /**
11040      * Returns the {@link TextClassifier} used by this TextView.
11041      * If no TextClassifier has been set, this TextView uses the default set by the
11042      * {@link TextClassificationManager}.
11043      */
11044     @NonNull
11045     public TextClassifier getTextClassifier() {
11046         if (mTextClassifier == null) {
11047             TextClassificationManager tcm =
11048                     mContext.getSystemService(TextClassificationManager.class);
11049             if (tcm != null) {
11050                 mTextClassifier = tcm.getTextClassifier();
11051             } else {
11052                 mTextClassifier = TextClassifier.NO_OP;
11053             }
11054         }
11055         return mTextClassifier;
11056     }
11057
11058     /**
11059      * @hide
11060      */
11061     protected void stopTextActionMode() {
11062         if (mEditor != null) {
11063             mEditor.stopTextActionMode();
11064         }
11065     }
11066
11067     boolean canUndo() {
11068         return mEditor != null && mEditor.canUndo();
11069     }
11070
11071     boolean canRedo() {
11072         return mEditor != null && mEditor.canRedo();
11073     }
11074
11075     boolean canCut() {
11076         if (hasPasswordTransformationMethod()) {
11077             return false;
11078         }
11079
11080         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
11081                 && mEditor.mKeyListener != null) {
11082             return true;
11083         }
11084
11085         return false;
11086     }
11087
11088     boolean canCopy() {
11089         if (hasPasswordTransformationMethod()) {
11090             return false;
11091         }
11092
11093         if (mText.length() > 0 && hasSelection() && mEditor != null) {
11094             return true;
11095         }
11096
11097         return false;
11098     }
11099
11100     boolean canShare() {
11101         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
11102             return false;
11103         }
11104         return canCopy();
11105     }
11106
11107     boolean isDeviceProvisioned() {
11108         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
11109             mDeviceProvisionedState = Settings.Global.getInt(
11110                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
11111                     ? DEVICE_PROVISIONED_YES
11112                     : DEVICE_PROVISIONED_NO;
11113         }
11114         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
11115     }
11116
11117     boolean canPaste() {
11118         return (mText instanceof Editable
11119                 && mEditor != null && mEditor.mKeyListener != null
11120                 && getSelectionStart() >= 0
11121                 && getSelectionEnd() >= 0
11122                 && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11123                         .hasPrimaryClip());
11124     }
11125
11126     boolean canPasteAsPlainText() {
11127         if (!canPaste()) {
11128             return false;
11129         }
11130
11131         final ClipData clipData =
11132                 ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11133                         .getPrimaryClip();
11134         final ClipDescription description = clipData.getDescription();
11135         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
11136         final CharSequence text = clipData.getItemAt(0).getText();
11137         if (isPlainType && (text instanceof Spanned)) {
11138             Spanned spanned = (Spanned) text;
11139             if (TextUtils.hasStyleSpan(spanned)) {
11140                 return true;
11141             }
11142         }
11143         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
11144     }
11145
11146     boolean canProcessText() {
11147         if (getId() == View.NO_ID) {
11148             return false;
11149         }
11150         return canShare();
11151     }
11152
11153     boolean canSelectAllText() {
11154         return canSelectText() && !hasPasswordTransformationMethod()
11155                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
11156     }
11157
11158     boolean selectAllText() {
11159         if (mEditor != null) {
11160             // Hide the toolbar before changing the selection to avoid flickering.
11161             mEditor.hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
11162         }
11163         final int length = mText.length();
11164         Selection.setSelection((Spannable) mText, 0, length);
11165         return length > 0;
11166     }
11167
11168     void replaceSelectionWithText(CharSequence text) {
11169         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
11170     }
11171
11172     /**
11173      * Paste clipboard content between min and max positions.
11174      */
11175     private void paste(int min, int max, boolean withFormatting) {
11176         ClipboardManager clipboard =
11177                 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11178         ClipData clip = clipboard.getPrimaryClip();
11179         if (clip != null) {
11180             boolean didFirst = false;
11181             for (int i = 0; i < clip.getItemCount(); i++) {
11182                 final CharSequence paste;
11183                 if (withFormatting) {
11184                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
11185                 } else {
11186                     // Get an item as text and remove all spans by toString().
11187                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
11188                     paste = (text instanceof Spanned) ? text.toString() : text;
11189                 }
11190                 if (paste != null) {
11191                     if (!didFirst) {
11192                         Selection.setSelection((Spannable) mText, max);
11193                         ((Editable) mText).replace(min, max, paste);
11194                         didFirst = true;
11195                     } else {
11196                         ((Editable) mText).insert(getSelectionEnd(), "\n");
11197                         ((Editable) mText).insert(getSelectionEnd(), paste);
11198                     }
11199                 }
11200             }
11201             sLastCutCopyOrTextChangedTime = 0;
11202         }
11203     }
11204
11205     private void shareSelectedText() {
11206         String selectedText = getSelectedText();
11207         if (selectedText != null && !selectedText.isEmpty()) {
11208             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
11209             sharingIntent.setType("text/plain");
11210             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
11211             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
11212             getContext().startActivity(Intent.createChooser(sharingIntent, null));
11213             Selection.setSelection((Spannable) mText, getSelectionEnd());
11214         }
11215     }
11216
11217     private void setPrimaryClip(ClipData clip) {
11218         ClipboardManager clipboard =
11219                 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11220         clipboard.setPrimaryClip(clip);
11221         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
11222     }
11223
11224     /**
11225      * Get the character offset closest to the specified absolute position. A typical use case is to
11226      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
11227      *
11228      * @param x The horizontal absolute position of a point on screen
11229      * @param y The vertical absolute position of a point on screen
11230      * @return the character offset for the character whose position is closest to the specified
11231      *  position. Returns -1 if there is no layout.
11232      */
11233     public int getOffsetForPosition(float x, float y) {
11234         if (getLayout() == null) return -1;
11235         final int line = getLineAtCoordinate(y);
11236         final int offset = getOffsetAtCoordinate(line, x);
11237         return offset;
11238     }
11239
11240     float convertToLocalHorizontalCoordinate(float x) {
11241         x -= getTotalPaddingLeft();
11242         // Clamp the position to inside of the view.
11243         x = Math.max(0.0f, x);
11244         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
11245         x += getScrollX();
11246         return x;
11247     }
11248
11249     int getLineAtCoordinate(float y) {
11250         y -= getTotalPaddingTop();
11251         // Clamp the position to inside of the view.
11252         y = Math.max(0.0f, y);
11253         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
11254         y += getScrollY();
11255         return getLayout().getLineForVertical((int) y);
11256     }
11257
11258     int getLineAtCoordinateUnclamped(float y) {
11259         y -= getTotalPaddingTop();
11260         y += getScrollY();
11261         return getLayout().getLineForVertical((int) y);
11262     }
11263
11264     int getOffsetAtCoordinate(int line, float x) {
11265         x = convertToLocalHorizontalCoordinate(x);
11266         return getLayout().getOffsetForHorizontal(line, x);
11267     }
11268
11269     @Override
11270     public boolean onDragEvent(DragEvent event) {
11271         switch (event.getAction()) {
11272             case DragEvent.ACTION_DRAG_STARTED:
11273                 return mEditor != null && mEditor.hasInsertionController();
11274
11275             case DragEvent.ACTION_DRAG_ENTERED:
11276                 TextView.this.requestFocus();
11277                 return true;
11278
11279             case DragEvent.ACTION_DRAG_LOCATION:
11280                 if (mText instanceof Spannable) {
11281                     final int offset = getOffsetForPosition(event.getX(), event.getY());
11282                     Selection.setSelection((Spannable) mText, offset);
11283                 }
11284                 return true;
11285
11286             case DragEvent.ACTION_DROP:
11287                 if (mEditor != null) mEditor.onDrop(event);
11288                 return true;
11289
11290             case DragEvent.ACTION_DRAG_ENDED:
11291             case DragEvent.ACTION_DRAG_EXITED:
11292             default:
11293                 return true;
11294         }
11295     }
11296
11297     boolean isInBatchEditMode() {
11298         if (mEditor == null) return false;
11299         final Editor.InputMethodState ims = mEditor.mInputMethodState;
11300         if (ims != null) {
11301             return ims.mBatchEditNesting > 0;
11302         }
11303         return mEditor.mInBatchEditControllers;
11304     }
11305
11306     @Override
11307     public void onRtlPropertiesChanged(int layoutDirection) {
11308         super.onRtlPropertiesChanged(layoutDirection);
11309
11310         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
11311         if (mTextDir != newTextDir) {
11312             mTextDir = newTextDir;
11313             if (mLayout != null) {
11314                 checkForRelayout();
11315             }
11316         }
11317     }
11318
11319     /**
11320      * @hide
11321      */
11322     protected TextDirectionHeuristic getTextDirectionHeuristic() {
11323         if (hasPasswordTransformationMethod()) {
11324             // passwords fields should be LTR
11325             return TextDirectionHeuristics.LTR;
11326         }
11327
11328         if (mEditor != null
11329                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
11330                     == EditorInfo.TYPE_CLASS_PHONE) {
11331             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
11332             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
11333             // RTL digits.
11334             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
11335             final String zero = symbols.getDigitStrings()[0];
11336             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
11337             // direction.
11338             final int firstCodepoint = zero.codePointAt(0);
11339             final byte digitDirection = Character.getDirectionality(firstCodepoint);
11340             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
11341                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
11342                 return TextDirectionHeuristics.RTL;
11343             } else {
11344                 return TextDirectionHeuristics.LTR;
11345             }
11346         }
11347
11348         // Always need to resolve layout direction first
11349         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
11350
11351         // Now, we can select the heuristic
11352         switch (getTextDirection()) {
11353             default:
11354             case TEXT_DIRECTION_FIRST_STRONG:
11355                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11356                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
11357             case TEXT_DIRECTION_ANY_RTL:
11358                 return TextDirectionHeuristics.ANYRTL_LTR;
11359             case TEXT_DIRECTION_LTR:
11360                 return TextDirectionHeuristics.LTR;
11361             case TEXT_DIRECTION_RTL:
11362                 return TextDirectionHeuristics.RTL;
11363             case TEXT_DIRECTION_LOCALE:
11364                 return TextDirectionHeuristics.LOCALE;
11365             case TEXT_DIRECTION_FIRST_STRONG_LTR:
11366                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
11367             case TEXT_DIRECTION_FIRST_STRONG_RTL:
11368                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
11369         }
11370     }
11371
11372     /**
11373      * @hide
11374      */
11375     @Override
11376     public void onResolveDrawables(int layoutDirection) {
11377         // No need to resolve twice
11378         if (mLastLayoutDirection == layoutDirection) {
11379             return;
11380         }
11381         mLastLayoutDirection = layoutDirection;
11382
11383         // Resolve drawables
11384         if (mDrawables != null) {
11385             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
11386                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
11387                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
11388                 applyCompoundDrawableTint();
11389             }
11390         }
11391     }
11392
11393     /**
11394      * Prepares a drawable for display by propagating layout direction and
11395      * drawable state.
11396      *
11397      * @param dr the drawable to prepare
11398      */
11399     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
11400         if (dr == null) {
11401             return;
11402         }
11403
11404         dr.setLayoutDirection(getLayoutDirection());
11405
11406         if (dr.isStateful()) {
11407             dr.setState(getDrawableState());
11408             dr.jumpToCurrentState();
11409         }
11410     }
11411
11412     /**
11413      * @hide
11414      */
11415     protected void resetResolvedDrawables() {
11416         super.resetResolvedDrawables();
11417         mLastLayoutDirection = -1;
11418     }
11419
11420     /**
11421      * @hide
11422      */
11423     protected void viewClicked(InputMethodManager imm) {
11424         if (imm != null) {
11425             imm.viewClicked(this);
11426         }
11427     }
11428
11429     /**
11430      * Deletes the range of text [start, end[.
11431      * @hide
11432      */
11433     protected void deleteText_internal(int start, int end) {
11434         ((Editable) mText).delete(start, end);
11435     }
11436
11437     /**
11438      * Replaces the range of text [start, end[ by replacement text
11439      * @hide
11440      */
11441     protected void replaceText_internal(int start, int end, CharSequence text) {
11442         ((Editable) mText).replace(start, end, text);
11443     }
11444
11445     /**
11446      * Sets a span on the specified range of text
11447      * @hide
11448      */
11449     protected void setSpan_internal(Object span, int start, int end, int flags) {
11450         ((Editable) mText).setSpan(span, start, end, flags);
11451     }
11452
11453     /**
11454      * Moves the cursor to the specified offset position in text
11455      * @hide
11456      */
11457     protected void setCursorPosition_internal(int start, int end) {
11458         Selection.setSelection(((Editable) mText), start, end);
11459     }
11460
11461     /**
11462      * An Editor should be created as soon as any of the editable-specific fields (grouped
11463      * inside the Editor object) is assigned to a non-default value.
11464      * This method will create the Editor if needed.
11465      *
11466      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
11467      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
11468      * Editor for backward compatibility, as soon as one of these fields is assigned.
11469      *
11470      * Also note that for performance reasons, the mEditor is created when needed, but not
11471      * reset when no more edit-specific fields are needed.
11472      */
11473     private void createEditorIfNeeded() {
11474         if (mEditor == null) {
11475             mEditor = new Editor(this);
11476         }
11477     }
11478
11479     /**
11480      * @hide
11481      */
11482     @Override
11483     public CharSequence getIterableTextForAccessibility() {
11484         return mText;
11485     }
11486
11487     private void ensureIterableTextForAccessibilitySelectable() {
11488         if (!(mText instanceof Spannable)) {
11489             setText(mText, BufferType.SPANNABLE);
11490         }
11491     }
11492
11493     /**
11494      * @hide
11495      */
11496     @Override
11497     public TextSegmentIterator getIteratorForGranularity(int granularity) {
11498         switch (granularity) {
11499             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
11500                 Spannable text = (Spannable) getIterableTextForAccessibility();
11501                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
11502                     AccessibilityIterators.LineTextSegmentIterator iterator =
11503                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
11504                     iterator.initialize(text, getLayout());
11505                     return iterator;
11506                 }
11507             } break;
11508             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
11509                 Spannable text = (Spannable) getIterableTextForAccessibility();
11510                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
11511                     AccessibilityIterators.PageTextSegmentIterator iterator =
11512                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
11513                     iterator.initialize(this);
11514                     return iterator;
11515                 }
11516             } break;
11517         }
11518         return super.getIteratorForGranularity(granularity);
11519     }
11520
11521     /**
11522      * @hide
11523      */
11524     @Override
11525     public int getAccessibilitySelectionStart() {
11526         return getSelectionStart();
11527     }
11528
11529     /**
11530      * @hide
11531      */
11532     public boolean isAccessibilitySelectionExtendable() {
11533         return true;
11534     }
11535
11536     /**
11537      * @hide
11538      */
11539     @Override
11540     public int getAccessibilitySelectionEnd() {
11541         return getSelectionEnd();
11542     }
11543
11544     /**
11545      * @hide
11546      */
11547     @Override
11548     public void setAccessibilitySelection(int start, int end) {
11549         if (getAccessibilitySelectionStart() == start
11550                 && getAccessibilitySelectionEnd() == end) {
11551             return;
11552         }
11553         CharSequence text = getIterableTextForAccessibility();
11554         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
11555             Selection.setSelection((Spannable) text, start, end);
11556         } else {
11557             Selection.removeSelection((Spannable) text);
11558         }
11559         // Hide all selection controllers used for adjusting selection
11560         // since we are doing so explicitlty by other means and these
11561         // controllers interact with how selection behaves.
11562         if (mEditor != null) {
11563             mEditor.hideCursorAndSpanControllers();
11564             mEditor.stopTextActionMode();
11565         }
11566     }
11567
11568     /** @hide */
11569     @Override
11570     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
11571         super.encodeProperties(stream);
11572
11573         TruncateAt ellipsize = getEllipsize();
11574         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
11575         stream.addProperty("text:textSize", getTextSize());
11576         stream.addProperty("text:scaledTextSize", getScaledTextSize());
11577         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
11578         stream.addProperty("text:selectionStart", getSelectionStart());
11579         stream.addProperty("text:selectionEnd", getSelectionEnd());
11580         stream.addProperty("text:curTextColor", mCurTextColor);
11581         stream.addProperty("text:text", mText == null ? null : mText.toString());
11582         stream.addProperty("text:gravity", mGravity);
11583     }
11584
11585     /**
11586      * User interface state that is stored by TextView for implementing
11587      * {@link View#onSaveInstanceState}.
11588      */
11589     public static class SavedState extends BaseSavedState {
11590         int selStart = -1;
11591         int selEnd = -1;
11592         CharSequence text;
11593         boolean frozenWithFocus;
11594         CharSequence error;
11595         ParcelableParcel editorState;  // Optional state from Editor.
11596
11597         SavedState(Parcelable superState) {
11598             super(superState);
11599         }
11600
11601         @Override
11602         public void writeToParcel(Parcel out, int flags) {
11603             super.writeToParcel(out, flags);
11604             out.writeInt(selStart);
11605             out.writeInt(selEnd);
11606             out.writeInt(frozenWithFocus ? 1 : 0);
11607             TextUtils.writeToParcel(text, out, flags);
11608
11609             if (error == null) {
11610                 out.writeInt(0);
11611             } else {
11612                 out.writeInt(1);
11613                 TextUtils.writeToParcel(error, out, flags);
11614             }
11615
11616             if (editorState == null) {
11617                 out.writeInt(0);
11618             } else {
11619                 out.writeInt(1);
11620                 editorState.writeToParcel(out, flags);
11621             }
11622         }
11623
11624         @Override
11625         public String toString() {
11626             String str = "TextView.SavedState{"
11627                     + Integer.toHexString(System.identityHashCode(this))
11628                     + " start=" + selStart + " end=" + selEnd;
11629             if (text != null) {
11630                 str += " text=" + text;
11631             }
11632             return str + "}";
11633         }
11634
11635         @SuppressWarnings("hiding")
11636         public static final Parcelable.Creator<SavedState> CREATOR =
11637                 new Parcelable.Creator<SavedState>() {
11638                     public SavedState createFromParcel(Parcel in) {
11639                         return new SavedState(in);
11640                     }
11641
11642                     public SavedState[] newArray(int size) {
11643                         return new SavedState[size];
11644                     }
11645                 };
11646
11647         private SavedState(Parcel in) {
11648             super(in);
11649             selStart = in.readInt();
11650             selEnd = in.readInt();
11651             frozenWithFocus = (in.readInt() != 0);
11652             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11653
11654             if (in.readInt() != 0) {
11655                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11656             }
11657
11658             if (in.readInt() != 0) {
11659                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
11660             }
11661         }
11662     }
11663
11664     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
11665         private char[] mChars;
11666         private int mStart, mLength;
11667
11668         public CharWrapper(char[] chars, int start, int len) {
11669             mChars = chars;
11670             mStart = start;
11671             mLength = len;
11672         }
11673
11674         /* package */ void set(char[] chars, int start, int len) {
11675             mChars = chars;
11676             mStart = start;
11677             mLength = len;
11678         }
11679
11680         public int length() {
11681             return mLength;
11682         }
11683
11684         public char charAt(int off) {
11685             return mChars[off + mStart];
11686         }
11687
11688         @Override
11689         public String toString() {
11690             return new String(mChars, mStart, mLength);
11691         }
11692
11693         public CharSequence subSequence(int start, int end) {
11694             if (start < 0 || end < 0 || start > mLength || end > mLength) {
11695                 throw new IndexOutOfBoundsException(start + ", " + end);
11696             }
11697
11698             return new String(mChars, start + mStart, end - start);
11699         }
11700
11701         public void getChars(int start, int end, char[] buf, int off) {
11702             if (start < 0 || end < 0 || start > mLength || end > mLength) {
11703                 throw new IndexOutOfBoundsException(start + ", " + end);
11704             }
11705
11706             System.arraycopy(mChars, start + mStart, buf, off, end - start);
11707         }
11708
11709         @Override
11710         public void drawText(BaseCanvas c, int start, int end,
11711                              float x, float y, Paint p) {
11712             c.drawText(mChars, start + mStart, end - start, x, y, p);
11713         }
11714
11715         @Override
11716         public void drawTextRun(BaseCanvas c, int start, int end,
11717                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
11718             int count = end - start;
11719             int contextCount = contextEnd - contextStart;
11720             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
11721                     contextCount, x, y, isRtl, p);
11722         }
11723
11724         public float measureText(int start, int end, Paint p) {
11725             return p.measureText(mChars, start + mStart, end - start);
11726         }
11727
11728         public int getTextWidths(int start, int end, float[] widths, Paint p) {
11729             return p.getTextWidths(mChars, start + mStart, end - start, widths);
11730         }
11731
11732         public float getTextRunAdvances(int start, int end, int contextStart,
11733                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
11734                 Paint p) {
11735             int count = end - start;
11736             int contextCount = contextEnd - contextStart;
11737             return p.getTextRunAdvances(mChars, start + mStart, count,
11738                     contextStart + mStart, contextCount, isRtl, advances,
11739                     advancesIndex);
11740         }
11741
11742         public int getTextRunCursor(int contextStart, int contextEnd, int dir,
11743                 int offset, int cursorOpt, Paint p) {
11744             int contextCount = contextEnd - contextStart;
11745             return p.getTextRunCursor(mChars, contextStart + mStart,
11746                     contextCount, dir, offset + mStart, cursorOpt);
11747         }
11748     }
11749
11750     private static final class Marquee {
11751         // TODO: Add an option to configure this
11752         private static final float MARQUEE_DELTA_MAX = 0.07f;
11753         private static final int MARQUEE_DELAY = 1200;
11754         private static final int MARQUEE_DP_PER_SECOND = 30;
11755
11756         private static final byte MARQUEE_STOPPED = 0x0;
11757         private static final byte MARQUEE_STARTING = 0x1;
11758         private static final byte MARQUEE_RUNNING = 0x2;
11759
11760         private final WeakReference<TextView> mView;
11761         private final Choreographer mChoreographer;
11762
11763         private byte mStatus = MARQUEE_STOPPED;
11764         private final float mPixelsPerSecond;
11765         private float mMaxScroll;
11766         private float mMaxFadeScroll;
11767         private float mGhostStart;
11768         private float mGhostOffset;
11769         private float mFadeStop;
11770         private int mRepeatLimit;
11771
11772         private float mScroll;
11773         private long mLastAnimationMs;
11774
11775         Marquee(TextView v) {
11776             final float density = v.getContext().getResources().getDisplayMetrics().density;
11777             mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
11778             mView = new WeakReference<TextView>(v);
11779             mChoreographer = Choreographer.getInstance();
11780         }
11781
11782         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
11783             @Override
11784             public void doFrame(long frameTimeNanos) {
11785                 tick();
11786             }
11787         };
11788
11789         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
11790             @Override
11791             public void doFrame(long frameTimeNanos) {
11792                 mStatus = MARQUEE_RUNNING;
11793                 mLastAnimationMs = mChoreographer.getFrameTime();
11794                 tick();
11795             }
11796         };
11797
11798         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
11799             @Override
11800             public void doFrame(long frameTimeNanos) {
11801                 if (mStatus == MARQUEE_RUNNING) {
11802                     if (mRepeatLimit >= 0) {
11803                         mRepeatLimit--;
11804                     }
11805                     start(mRepeatLimit);
11806                 }
11807             }
11808         };
11809
11810         void tick() {
11811             if (mStatus != MARQUEE_RUNNING) {
11812                 return;
11813             }
11814
11815             mChoreographer.removeFrameCallback(mTickCallback);
11816
11817             final TextView textView = mView.get();
11818             if (textView != null && (textView.isFocused() || textView.isSelected())) {
11819                 long currentMs = mChoreographer.getFrameTime();
11820                 long deltaMs = currentMs - mLastAnimationMs;
11821                 mLastAnimationMs = currentMs;
11822                 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
11823                 mScroll += deltaPx;
11824                 if (mScroll > mMaxScroll) {
11825                     mScroll = mMaxScroll;
11826                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
11827                 } else {
11828                     mChoreographer.postFrameCallback(mTickCallback);
11829                 }
11830                 textView.invalidate();
11831             }
11832         }
11833
11834         void stop() {
11835             mStatus = MARQUEE_STOPPED;
11836             mChoreographer.removeFrameCallback(mStartCallback);
11837             mChoreographer.removeFrameCallback(mRestartCallback);
11838             mChoreographer.removeFrameCallback(mTickCallback);
11839             resetScroll();
11840         }
11841
11842         private void resetScroll() {
11843             mScroll = 0.0f;
11844             final TextView textView = mView.get();
11845             if (textView != null) textView.invalidate();
11846         }
11847
11848         void start(int repeatLimit) {
11849             if (repeatLimit == 0) {
11850                 stop();
11851                 return;
11852             }
11853             mRepeatLimit = repeatLimit;
11854             final TextView textView = mView.get();
11855             if (textView != null && textView.mLayout != null) {
11856                 mStatus = MARQUEE_STARTING;
11857                 mScroll = 0.0f;
11858                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
11859                         - textView.getCompoundPaddingRight();
11860                 final float lineWidth = textView.mLayout.getLineWidth(0);
11861                 final float gap = textWidth / 3.0f;
11862                 mGhostStart = lineWidth - textWidth + gap;
11863                 mMaxScroll = mGhostStart + textWidth;
11864                 mGhostOffset = lineWidth + gap;
11865                 mFadeStop = lineWidth + textWidth / 6.0f;
11866                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
11867
11868                 textView.invalidate();
11869                 mChoreographer.postFrameCallback(mStartCallback);
11870             }
11871         }
11872
11873         float getGhostOffset() {
11874             return mGhostOffset;
11875         }
11876
11877         float getScroll() {
11878             return mScroll;
11879         }
11880
11881         float getMaxFadeScroll() {
11882             return mMaxFadeScroll;
11883         }
11884
11885         boolean shouldDrawLeftFade() {
11886             return mScroll <= mFadeStop;
11887         }
11888
11889         boolean shouldDrawGhost() {
11890             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
11891         }
11892
11893         boolean isRunning() {
11894             return mStatus == MARQUEE_RUNNING;
11895         }
11896
11897         boolean isStopped() {
11898             return mStatus == MARQUEE_STOPPED;
11899         }
11900     }
11901
11902     private class ChangeWatcher implements TextWatcher, SpanWatcher {
11903
11904         private CharSequence mBeforeText;
11905
11906         public void beforeTextChanged(CharSequence buffer, int start,
11907                                       int before, int after) {
11908             if (DEBUG_EXTRACT) {
11909                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
11910                         + " before=" + before + " after=" + after + ": " + buffer);
11911             }
11912
11913             if (AccessibilityManager.getInstance(mContext).isEnabled()
11914                     && !isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) {
11915                 mBeforeText = buffer.toString();
11916             }
11917
11918             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
11919         }
11920
11921         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
11922             if (DEBUG_EXTRACT) {
11923                 Log.v(LOG_TAG, "onTextChanged start=" + start
11924                         + " before=" + before + " after=" + after + ": " + buffer);
11925             }
11926             TextView.this.handleTextChanged(buffer, start, before, after);
11927
11928             if (AccessibilityManager.getInstance(mContext).isEnabled()
11929                     && (isFocused() || isSelected() && isShown())) {
11930                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
11931                 mBeforeText = null;
11932             }
11933         }
11934
11935         public void afterTextChanged(Editable buffer) {
11936             if (DEBUG_EXTRACT) {
11937                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
11938             }
11939             TextView.this.sendAfterTextChanged(buffer);
11940
11941             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
11942                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
11943             }
11944         }
11945
11946         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
11947             if (DEBUG_EXTRACT) {
11948                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
11949                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
11950             }
11951             TextView.this.spanChange(buf, what, s, st, e, en);
11952         }
11953
11954         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
11955             if (DEBUG_EXTRACT) {
11956                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
11957             }
11958             TextView.this.spanChange(buf, what, -1, s, -1, e);
11959         }
11960
11961         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
11962             if (DEBUG_EXTRACT) {
11963                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
11964             }
11965             TextView.this.spanChange(buf, what, s, -1, e, -1);
11966         }
11967     }
11968 }