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