OSDN Git Service

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