OSDN Git Service

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