• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
3258     @android.view.RemotableViewMethod
setTextColor(ColorStateList colors)3259     public void setTextColor(ColorStateList colors) {
3260         if (colors == null) {
3261             throw new NullPointerException();
3262         }
3263 
3264         mTextColor = colors;
3265         updateTextColors();
3266     }
3267 
3268     /**
3269      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3270      *
3271      * @see #setTextColor(ColorStateList)
3272      * @see #setTextColor(int)
3273      *
3274      * @attr ref android.R.styleable#TextView_textColor
3275      */
getTextColors()3276     public final ColorStateList getTextColors() {
3277         return mTextColor;
3278     }
3279 
3280     /**
3281      * <p>Return the current color selected for normal text.</p>
3282      *
3283      * @return Returns the current text color.
3284      */
3285     @ColorInt
getCurrentTextColor()3286     public final int getCurrentTextColor() {
3287         return mCurTextColor;
3288     }
3289 
3290     /**
3291      * Sets the color used to display the selection highlight.
3292      *
3293      * @attr ref android.R.styleable#TextView_textColorHighlight
3294      */
3295     @android.view.RemotableViewMethod
setHighlightColor(@olorInt int color)3296     public void setHighlightColor(@ColorInt int color) {
3297         if (mHighlightColor != color) {
3298             mHighlightColor = color;
3299             invalidate();
3300         }
3301     }
3302 
3303     /**
3304      * @return the color used to display the selection highlight
3305      *
3306      * @see #setHighlightColor(int)
3307      *
3308      * @attr ref android.R.styleable#TextView_textColorHighlight
3309      */
3310     @ColorInt
getHighlightColor()3311     public int getHighlightColor() {
3312         return mHighlightColor;
3313     }
3314 
3315     /**
3316      * Sets whether the soft input method will be made visible when this
3317      * TextView gets focused. The default is true.
3318      */
3319     @android.view.RemotableViewMethod
setShowSoftInputOnFocus(boolean show)3320     public final void setShowSoftInputOnFocus(boolean show) {
3321         createEditorIfNeeded();
3322         mEditor.mShowSoftInputOnFocus = show;
3323     }
3324 
3325     /**
3326      * Returns whether the soft input method will be made visible when this
3327      * TextView gets focused. The default is true.
3328      */
getShowSoftInputOnFocus()3329     public final boolean getShowSoftInputOnFocus() {
3330         // When there is no Editor, return default true value
3331         return mEditor == null || mEditor.mShowSoftInputOnFocus;
3332     }
3333 
3334     /**
3335      * Gives the text a shadow of the specified blur radius and color, the specified
3336      * distance from its drawn position.
3337      * <p>
3338      * The text shadow produced does not interact with the properties on view
3339      * that are responsible for real time shadows,
3340      * {@link View#getElevation() elevation} and
3341      * {@link View#getTranslationZ() translationZ}.
3342      *
3343      * @see Paint#setShadowLayer(float, float, float, int)
3344      *
3345      * @attr ref android.R.styleable#TextView_shadowColor
3346      * @attr ref android.R.styleable#TextView_shadowDx
3347      * @attr ref android.R.styleable#TextView_shadowDy
3348      * @attr ref android.R.styleable#TextView_shadowRadius
3349      */
setShadowLayer(float radius, float dx, float dy, int color)3350     public void setShadowLayer(float radius, float dx, float dy, int color) {
3351         mTextPaint.setShadowLayer(radius, dx, dy, color);
3352 
3353         mShadowRadius = radius;
3354         mShadowDx = dx;
3355         mShadowDy = dy;
3356         mShadowColor = color;
3357 
3358         // Will change text clip region
3359         if (mEditor != null) {
3360             mEditor.invalidateTextDisplayList();
3361             mEditor.invalidateHandlesAndActionMode();
3362         }
3363         invalidate();
3364     }
3365 
3366     /**
3367      * Gets the radius of the shadow layer.
3368      *
3369      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3370      *
3371      * @see #setShadowLayer(float, float, float, int)
3372      *
3373      * @attr ref android.R.styleable#TextView_shadowRadius
3374      */
getShadowRadius()3375     public float getShadowRadius() {
3376         return mShadowRadius;
3377     }
3378 
3379     /**
3380      * @return the horizontal offset of the shadow layer
3381      *
3382      * @see #setShadowLayer(float, float, float, int)
3383      *
3384      * @attr ref android.R.styleable#TextView_shadowDx
3385      */
getShadowDx()3386     public float getShadowDx() {
3387         return mShadowDx;
3388     }
3389 
3390     /**
3391      * @return the vertical offset of the shadow layer
3392      *
3393      * @see #setShadowLayer(float, float, float, int)
3394      *
3395      * @attr ref android.R.styleable#TextView_shadowDy
3396      */
getShadowDy()3397     public float getShadowDy() {
3398         return mShadowDy;
3399     }
3400 
3401     /**
3402      * @return the color of the shadow layer
3403      *
3404      * @see #setShadowLayer(float, float, float, int)
3405      *
3406      * @attr ref android.R.styleable#TextView_shadowColor
3407      */
3408     @ColorInt
getShadowColor()3409     public int getShadowColor() {
3410         return mShadowColor;
3411     }
3412 
3413     /**
3414      * @return the base paint used for the text.  Please use this only to
3415      * consult the Paint's properties and not to change them.
3416      */
getPaint()3417     public TextPaint getPaint() {
3418         return mTextPaint;
3419     }
3420 
3421     /**
3422      * Sets the autolink mask of the text.  See {@link
3423      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3424      * possible values.
3425      *
3426      * @attr ref android.R.styleable#TextView_autoLink
3427      */
3428     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)3429     public final void setAutoLinkMask(int mask) {
3430         mAutoLinkMask = mask;
3431     }
3432 
3433     /**
3434      * Sets whether the movement method will automatically be set to
3435      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3436      * set to nonzero and links are detected in {@link #setText}.
3437      * The default is true.
3438      *
3439      * @attr ref android.R.styleable#TextView_linksClickable
3440      */
3441     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)3442     public final void setLinksClickable(boolean whether) {
3443         mLinksClickable = whether;
3444     }
3445 
3446     /**
3447      * Returns whether the movement method will automatically be set to
3448      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3449      * set to nonzero and links are detected in {@link #setText}.
3450      * The default is true.
3451      *
3452      * @attr ref android.R.styleable#TextView_linksClickable
3453      */
getLinksClickable()3454     public final boolean getLinksClickable() {
3455         return mLinksClickable;
3456     }
3457 
3458     /**
3459      * Returns the list of URLSpans attached to the text
3460      * (by {@link Linkify} or otherwise) if any.  You can call
3461      * {@link URLSpan#getURL} on them to find where they link to
3462      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3463      * to find the region of the text they are attached to.
3464      */
getUrls()3465     public URLSpan[] getUrls() {
3466         if (mText instanceof Spanned) {
3467             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3468         } else {
3469             return new URLSpan[0];
3470         }
3471     }
3472 
3473     /**
3474      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3475      * TextView.
3476      *
3477      * @see #setHintTextColor(ColorStateList)
3478      * @see #getHintTextColors()
3479      * @see #setTextColor(int)
3480      *
3481      * @attr ref android.R.styleable#TextView_textColorHint
3482      */
3483     @android.view.RemotableViewMethod
setHintTextColor(@olorInt int color)3484     public final void setHintTextColor(@ColorInt int color) {
3485         mHintTextColor = ColorStateList.valueOf(color);
3486         updateTextColors();
3487     }
3488 
3489     /**
3490      * Sets the color of the hint text.
3491      *
3492      * @see #getHintTextColors()
3493      * @see #setHintTextColor(int)
3494      * @see #setTextColor(ColorStateList)
3495      * @see #setLinkTextColor(ColorStateList)
3496      *
3497      * @attr ref android.R.styleable#TextView_textColorHint
3498      */
setHintTextColor(ColorStateList colors)3499     public final void setHintTextColor(ColorStateList colors) {
3500         mHintTextColor = colors;
3501         updateTextColors();
3502     }
3503 
3504     /**
3505      * @return the color of the hint text, for the different states of this TextView.
3506      *
3507      * @see #setHintTextColor(ColorStateList)
3508      * @see #setHintTextColor(int)
3509      * @see #setTextColor(ColorStateList)
3510      * @see #setLinkTextColor(ColorStateList)
3511      *
3512      * @attr ref android.R.styleable#TextView_textColorHint
3513      */
getHintTextColors()3514     public final ColorStateList getHintTextColors() {
3515         return mHintTextColor;
3516     }
3517 
3518     /**
3519      * <p>Return the current color selected to paint the hint text.</p>
3520      *
3521      * @return Returns the current hint text color.
3522      */
3523     @ColorInt
getCurrentHintTextColor()3524     public final int getCurrentHintTextColor() {
3525         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3526     }
3527 
3528     /**
3529      * Sets the color of links in the text.
3530      *
3531      * @see #setLinkTextColor(ColorStateList)
3532      * @see #getLinkTextColors()
3533      *
3534      * @attr ref android.R.styleable#TextView_textColorLink
3535      */
3536     @android.view.RemotableViewMethod
setLinkTextColor(@olorInt int color)3537     public final void setLinkTextColor(@ColorInt int color) {
3538         mLinkTextColor = ColorStateList.valueOf(color);
3539         updateTextColors();
3540     }
3541 
3542     /**
3543      * Sets the color of links in the text.
3544      *
3545      * @see #setLinkTextColor(int)
3546      * @see #getLinkTextColors()
3547      * @see #setTextColor(ColorStateList)
3548      * @see #setHintTextColor(ColorStateList)
3549      *
3550      * @attr ref android.R.styleable#TextView_textColorLink
3551      */
setLinkTextColor(ColorStateList colors)3552     public final void setLinkTextColor(ColorStateList colors) {
3553         mLinkTextColor = colors;
3554         updateTextColors();
3555     }
3556 
3557     /**
3558      * @return the list of colors used to paint the links in the text, for the different states of
3559      * this TextView
3560      *
3561      * @see #setLinkTextColor(ColorStateList)
3562      * @see #setLinkTextColor(int)
3563      *
3564      * @attr ref android.R.styleable#TextView_textColorLink
3565      */
getLinkTextColors()3566     public final ColorStateList getLinkTextColors() {
3567         return mLinkTextColor;
3568     }
3569 
3570     /**
3571      * Sets the horizontal alignment of the text and the
3572      * vertical gravity that will be used when there is extra space
3573      * in the TextView beyond what is required for the text itself.
3574      *
3575      * @see android.view.Gravity
3576      * @attr ref android.R.styleable#TextView_gravity
3577      */
setGravity(int gravity)3578     public void setGravity(int gravity) {
3579         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3580             gravity |= Gravity.START;
3581         }
3582         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3583             gravity |= Gravity.TOP;
3584         }
3585 
3586         boolean newLayout = false;
3587 
3588         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3589             (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3590             newLayout = true;
3591         }
3592 
3593         if (gravity != mGravity) {
3594             invalidate();
3595         }
3596 
3597         mGravity = gravity;
3598 
3599         if (mLayout != null && newLayout) {
3600             // XXX this is heavy-handed because no actual content changes.
3601             int want = mLayout.getWidth();
3602             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3603 
3604             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3605                           mRight - mLeft - getCompoundPaddingLeft() -
3606                           getCompoundPaddingRight(), true);
3607         }
3608     }
3609 
3610     /**
3611      * Returns the horizontal and vertical alignment of this TextView.
3612      *
3613      * @see android.view.Gravity
3614      * @attr ref android.R.styleable#TextView_gravity
3615      */
getGravity()3616     public int getGravity() {
3617         return mGravity;
3618     }
3619 
3620     /**
3621      * @return the flags on the Paint being used to display the text.
3622      * @see Paint#getFlags
3623      */
getPaintFlags()3624     public int getPaintFlags() {
3625         return mTextPaint.getFlags();
3626     }
3627 
3628     /**
3629      * Sets flags on the Paint being used to display the text and
3630      * reflows the text if they are different from the old flags.
3631      * @see Paint#setFlags
3632      */
3633     @android.view.RemotableViewMethod
setPaintFlags(int flags)3634     public void setPaintFlags(int flags) {
3635         if (mTextPaint.getFlags() != flags) {
3636             mTextPaint.setFlags(flags);
3637 
3638             if (mLayout != null) {
3639                 nullLayouts();
3640                 requestLayout();
3641                 invalidate();
3642             }
3643         }
3644     }
3645 
3646     /**
3647      * Sets whether the text should be allowed to be wider than the
3648      * View is.  If false, it will be wrapped to the width of the View.
3649      *
3650      * @attr ref android.R.styleable#TextView_scrollHorizontally
3651      */
setHorizontallyScrolling(boolean whether)3652     public void setHorizontallyScrolling(boolean whether) {
3653         if (mHorizontallyScrolling != whether) {
3654             mHorizontallyScrolling = whether;
3655 
3656             if (mLayout != null) {
3657                 nullLayouts();
3658                 requestLayout();
3659                 invalidate();
3660             }
3661         }
3662     }
3663 
3664     /**
3665      * Returns whether the text is allowed to be wider than the View is.
3666      * If false, the text will be wrapped to the width of the View.
3667      *
3668      * @attr ref android.R.styleable#TextView_scrollHorizontally
3669      * @hide
3670      */
getHorizontallyScrolling()3671     public boolean getHorizontallyScrolling() {
3672         return mHorizontallyScrolling;
3673     }
3674 
3675     /**
3676      * Makes the TextView at least this many lines tall.
3677      *
3678      * Setting this value overrides any other (minimum) height setting. A single line TextView will
3679      * set this value to 1.
3680      *
3681      * @see #getMinLines()
3682      *
3683      * @attr ref android.R.styleable#TextView_minLines
3684      */
3685     @android.view.RemotableViewMethod
setMinLines(int minlines)3686     public void setMinLines(int minlines) {
3687         mMinimum = minlines;
3688         mMinMode = LINES;
3689 
3690         requestLayout();
3691         invalidate();
3692     }
3693 
3694     /**
3695      * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3696      * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3697      *
3698      * @see #setMinLines(int)
3699      *
3700      * @attr ref android.R.styleable#TextView_minLines
3701      */
getMinLines()3702     public int getMinLines() {
3703         return mMinMode == LINES ? mMinimum : -1;
3704     }
3705 
3706     /**
3707      * Makes the TextView at least this many pixels tall.
3708      *
3709      * Setting this value overrides any other (minimum) number of lines setting.
3710      *
3711      * @attr ref android.R.styleable#TextView_minHeight
3712      */
3713     @android.view.RemotableViewMethod
setMinHeight(int minHeight)3714     public void setMinHeight(int minHeight) {
3715         mMinimum = minHeight;
3716         mMinMode = PIXELS;
3717 
3718         requestLayout();
3719         invalidate();
3720     }
3721 
3722     /**
3723      * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3724      * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3725      *
3726      * @see #setMinHeight(int)
3727      *
3728      * @attr ref android.R.styleable#TextView_minHeight
3729      */
getMinHeight()3730     public int getMinHeight() {
3731         return mMinMode == PIXELS ? mMinimum : -1;
3732     }
3733 
3734     /**
3735      * Makes the TextView at most this many lines tall.
3736      *
3737      * Setting this value overrides any other (maximum) height setting.
3738      *
3739      * @attr ref android.R.styleable#TextView_maxLines
3740      */
3741     @android.view.RemotableViewMethod
setMaxLines(int maxlines)3742     public void setMaxLines(int maxlines) {
3743         mMaximum = maxlines;
3744         mMaxMode = LINES;
3745 
3746         requestLayout();
3747         invalidate();
3748     }
3749 
3750     /**
3751      * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3752      * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3753      *
3754      * @see #setMaxLines(int)
3755      *
3756      * @attr ref android.R.styleable#TextView_maxLines
3757      */
getMaxLines()3758     public int getMaxLines() {
3759         return mMaxMode == LINES ? mMaximum : -1;
3760     }
3761 
3762     /**
3763      * Makes the TextView at most this many pixels tall.  This option is mutually exclusive with the
3764      * {@link #setMaxLines(int)} method.
3765      *
3766      * Setting this value overrides any other (maximum) number of lines setting.
3767      *
3768      * @attr ref android.R.styleable#TextView_maxHeight
3769      */
3770     @android.view.RemotableViewMethod
setMaxHeight(int maxHeight)3771     public void setMaxHeight(int maxHeight) {
3772         mMaximum = maxHeight;
3773         mMaxMode = PIXELS;
3774 
3775         requestLayout();
3776         invalidate();
3777     }
3778 
3779     /**
3780      * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3781      * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3782      *
3783      * @see #setMaxHeight(int)
3784      *
3785      * @attr ref android.R.styleable#TextView_maxHeight
3786      */
getMaxHeight()3787     public int getMaxHeight() {
3788         return mMaxMode == PIXELS ? mMaximum : -1;
3789     }
3790 
3791     /**
3792      * Makes the TextView exactly this many lines tall.
3793      *
3794      * Note that setting this value overrides any other (minimum / maximum) number of lines or
3795      * height setting. A single line TextView will set this value to 1.
3796      *
3797      * @attr ref android.R.styleable#TextView_lines
3798      */
3799     @android.view.RemotableViewMethod
setLines(int lines)3800     public void setLines(int lines) {
3801         mMaximum = mMinimum = lines;
3802         mMaxMode = mMinMode = LINES;
3803 
3804         requestLayout();
3805         invalidate();
3806     }
3807 
3808     /**
3809      * Makes the TextView exactly this many pixels tall.
3810      * You could do the same thing by specifying this number in the
3811      * LayoutParams.
3812      *
3813      * Note that setting this value overrides any other (minimum / maximum) number of lines or
3814      * height setting.
3815      *
3816      * @attr ref android.R.styleable#TextView_height
3817      */
3818     @android.view.RemotableViewMethod
setHeight(int pixels)3819     public void setHeight(int pixels) {
3820         mMaximum = mMinimum = pixels;
3821         mMaxMode = mMinMode = PIXELS;
3822 
3823         requestLayout();
3824         invalidate();
3825     }
3826 
3827     /**
3828      * Makes the TextView at least this many ems wide
3829      *
3830      * @attr ref android.R.styleable#TextView_minEms
3831      */
3832     @android.view.RemotableViewMethod
setMinEms(int minems)3833     public void setMinEms(int minems) {
3834         mMinWidth = minems;
3835         mMinWidthMode = EMS;
3836 
3837         requestLayout();
3838         invalidate();
3839     }
3840 
3841     /**
3842      * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3843      * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3844      *
3845      * @see #setMinEms(int)
3846      * @see #setEms(int)
3847      *
3848      * @attr ref android.R.styleable#TextView_minEms
3849      */
getMinEms()3850     public int getMinEms() {
3851         return mMinWidthMode == EMS ? mMinWidth : -1;
3852     }
3853 
3854     /**
3855      * Makes the TextView at least this many pixels wide
3856      *
3857      * @attr ref android.R.styleable#TextView_minWidth
3858      */
3859     @android.view.RemotableViewMethod
setMinWidth(int minpixels)3860     public void setMinWidth(int minpixels) {
3861         mMinWidth = minpixels;
3862         mMinWidthMode = PIXELS;
3863 
3864         requestLayout();
3865         invalidate();
3866     }
3867 
3868     /**
3869      * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3870      * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3871      *
3872      * @see #setMinWidth(int)
3873      * @see #setWidth(int)
3874      *
3875      * @attr ref android.R.styleable#TextView_minWidth
3876      */
getMinWidth()3877     public int getMinWidth() {
3878         return mMinWidthMode == PIXELS ? mMinWidth : -1;
3879     }
3880 
3881     /**
3882      * Makes the TextView at most this many ems wide
3883      *
3884      * @attr ref android.R.styleable#TextView_maxEms
3885      */
3886     @android.view.RemotableViewMethod
setMaxEms(int maxems)3887     public void setMaxEms(int maxems) {
3888         mMaxWidth = maxems;
3889         mMaxWidthMode = EMS;
3890 
3891         requestLayout();
3892         invalidate();
3893     }
3894 
3895     /**
3896      * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3897      * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3898      *
3899      * @see #setMaxEms(int)
3900      * @see #setEms(int)
3901      *
3902      * @attr ref android.R.styleable#TextView_maxEms
3903      */
getMaxEms()3904     public int getMaxEms() {
3905         return mMaxWidthMode == EMS ? mMaxWidth : -1;
3906     }
3907 
3908     /**
3909      * Makes the TextView at most this many pixels wide
3910      *
3911      * @attr ref android.R.styleable#TextView_maxWidth
3912      */
3913     @android.view.RemotableViewMethod
setMaxWidth(int maxpixels)3914     public void setMaxWidth(int maxpixels) {
3915         mMaxWidth = maxpixels;
3916         mMaxWidthMode = PIXELS;
3917 
3918         requestLayout();
3919         invalidate();
3920     }
3921 
3922     /**
3923      * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3924      * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3925      *
3926      * @see #setMaxWidth(int)
3927      * @see #setWidth(int)
3928      *
3929      * @attr ref android.R.styleable#TextView_maxWidth
3930      */
getMaxWidth()3931     public int getMaxWidth() {
3932         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3933     }
3934 
3935     /**
3936      * Makes the TextView exactly this many ems wide
3937      *
3938      * @see #setMaxEms(int)
3939      * @see #setMinEms(int)
3940      * @see #getMinEms()
3941      * @see #getMaxEms()
3942      *
3943      * @attr ref android.R.styleable#TextView_ems
3944      */
3945     @android.view.RemotableViewMethod
setEms(int ems)3946     public void setEms(int ems) {
3947         mMaxWidth = mMinWidth = ems;
3948         mMaxWidthMode = mMinWidthMode = EMS;
3949 
3950         requestLayout();
3951         invalidate();
3952     }
3953 
3954     /**
3955      * Makes the TextView exactly this many pixels wide.
3956      * You could do the same thing by specifying this number in the
3957      * LayoutParams.
3958      *
3959      * @see #setMaxWidth(int)
3960      * @see #setMinWidth(int)
3961      * @see #getMinWidth()
3962      * @see #getMaxWidth()
3963      *
3964      * @attr ref android.R.styleable#TextView_width
3965      */
3966     @android.view.RemotableViewMethod
setWidth(int pixels)3967     public void setWidth(int pixels) {
3968         mMaxWidth = mMinWidth = pixels;
3969         mMaxWidthMode = mMinWidthMode = PIXELS;
3970 
3971         requestLayout();
3972         invalidate();
3973     }
3974 
3975     /**
3976      * Sets line spacing for this TextView.  Each line will have its height
3977      * multiplied by <code>mult</code> and have <code>add</code> added to it.
3978      *
3979      * @attr ref android.R.styleable#TextView_lineSpacingExtra
3980      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3981      */
setLineSpacing(float add, float mult)3982     public void setLineSpacing(float add, float mult) {
3983         if (mSpacingAdd != add || mSpacingMult != mult) {
3984             mSpacingAdd = add;
3985             mSpacingMult = mult;
3986 
3987             if (mLayout != null) {
3988                 nullLayouts();
3989                 requestLayout();
3990                 invalidate();
3991             }
3992         }
3993     }
3994 
3995     /**
3996      * Gets the line spacing multiplier
3997      *
3998      * @return the value by which each line's height is multiplied to get its actual height.
3999      *
4000      * @see #setLineSpacing(float, float)
4001      * @see #getLineSpacingExtra()
4002      *
4003      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4004      */
getLineSpacingMultiplier()4005     public float getLineSpacingMultiplier() {
4006         return mSpacingMult;
4007     }
4008 
4009     /**
4010      * Gets the line spacing extra space
4011      *
4012      * @return the extra space that is added to the height of each lines of this TextView.
4013      *
4014      * @see #setLineSpacing(float, float)
4015      * @see #getLineSpacingMultiplier()
4016      *
4017      * @attr ref android.R.styleable#TextView_lineSpacingExtra
4018      */
getLineSpacingExtra()4019     public float getLineSpacingExtra() {
4020         return mSpacingAdd;
4021     }
4022 
4023     /**
4024      * Convenience method: Append the specified text to the TextView's
4025      * display buffer, upgrading it to BufferType.EDITABLE if it was
4026      * not already editable.
4027      */
append(CharSequence text)4028     public final void append(CharSequence text) {
4029         append(text, 0, text.length());
4030     }
4031 
4032     /**
4033      * Convenience method: Append the specified text slice to the TextView's
4034      * display buffer, upgrading it to BufferType.EDITABLE if it was
4035      * not already editable.
4036      */
append(CharSequence text, int start, int end)4037     public void append(CharSequence text, int start, int end) {
4038         if (!(mText instanceof Editable)) {
4039             setText(mText, BufferType.EDITABLE);
4040         }
4041 
4042         ((Editable) mText).append(text, start, end);
4043 
4044         if (mAutoLinkMask != 0) {
4045             boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4046             // Do not change the movement method for text that support text selection as it
4047             // would prevent an arbitrary cursor displacement.
4048             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
4049                 setMovementMethod(LinkMovementMethod.getInstance());
4050             }
4051         }
4052     }
4053 
updateTextColors()4054     private void updateTextColors() {
4055         boolean inval = false;
4056         int color = mTextColor.getColorForState(getDrawableState(), 0);
4057         if (color != mCurTextColor) {
4058             mCurTextColor = color;
4059             inval = true;
4060         }
4061         if (mLinkTextColor != null) {
4062             color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4063             if (color != mTextPaint.linkColor) {
4064                 mTextPaint.linkColor = color;
4065                 inval = true;
4066             }
4067         }
4068         if (mHintTextColor != null) {
4069             color = mHintTextColor.getColorForState(getDrawableState(), 0);
4070             if (color != mCurHintTextColor) {
4071                 mCurHintTextColor = color;
4072                 if (mText.length() == 0) {
4073                     inval = true;
4074                 }
4075             }
4076         }
4077         if (inval) {
4078             // Text needs to be redrawn with the new color
4079             if (mEditor != null) mEditor.invalidateTextDisplayList();
4080             invalidate();
4081         }
4082     }
4083 
4084     @Override
drawableStateChanged()4085     protected void drawableStateChanged() {
4086         super.drawableStateChanged();
4087 
4088         if (mTextColor != null && mTextColor.isStateful()
4089                 || (mHintTextColor != null && mHintTextColor.isStateful())
4090                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4091             updateTextColors();
4092         }
4093 
4094         if (mDrawables != null) {
4095             final int[] state = getDrawableState();
4096             for (Drawable dr : mDrawables.mShowing) {
4097                 if (dr != null && dr.isStateful() && dr.setState(state)) {
4098                     invalidateDrawable(dr);
4099                 }
4100             }
4101         }
4102     }
4103 
4104     @Override
drawableHotspotChanged(float x, float y)4105     public void drawableHotspotChanged(float x, float y) {
4106         super.drawableHotspotChanged(x, y);
4107 
4108         if (mDrawables != null) {
4109             for (Drawable dr : mDrawables.mShowing) {
4110                 if (dr != null) {
4111                     dr.setHotspot(x, y);
4112                 }
4113             }
4114         }
4115     }
4116 
4117     @Override
onSaveInstanceState()4118     public Parcelable onSaveInstanceState() {
4119         Parcelable superState = super.onSaveInstanceState();
4120 
4121         // Save state if we are forced to
4122         final boolean freezesText = getFreezesText();
4123         boolean hasSelection = false;
4124         int start = -1;
4125         int end = -1;
4126 
4127         if (mText != null) {
4128             start = getSelectionStart();
4129             end = getSelectionEnd();
4130             if (start >= 0 || end >= 0) {
4131                 // Or save state if there is a selection
4132                 hasSelection = true;
4133             }
4134         }
4135 
4136         if (freezesText || hasSelection) {
4137             SavedState ss = new SavedState(superState);
4138 
4139             if (freezesText) {
4140                 if (mText instanceof Spanned) {
4141                     final Spannable sp = new SpannableStringBuilder(mText);
4142 
4143                     if (mEditor != null) {
4144                         removeMisspelledSpans(sp);
4145                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
4146                     }
4147 
4148                     ss.text = sp;
4149                 } else {
4150                     ss.text = mText.toString();
4151                 }
4152             }
4153 
4154             if (hasSelection) {
4155                 // XXX Should also save the current scroll position!
4156                 ss.selStart = start;
4157                 ss.selEnd = end;
4158             }
4159 
4160             if (isFocused() && start >= 0 && end >= 0) {
4161                 ss.frozenWithFocus = true;
4162             }
4163 
4164             ss.error = getError();
4165 
4166             if (mEditor != null) {
4167                 ss.editorState = mEditor.saveInstanceState();
4168             }
4169             return ss;
4170         }
4171 
4172         return superState;
4173     }
4174 
removeMisspelledSpans(Spannable spannable)4175     void removeMisspelledSpans(Spannable spannable) {
4176         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4177                 SuggestionSpan.class);
4178         for (int i = 0; i < suggestionSpans.length; i++) {
4179             int flags = suggestionSpans[i].getFlags();
4180             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4181                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4182                 spannable.removeSpan(suggestionSpans[i]);
4183             }
4184         }
4185     }
4186 
4187     @Override
onRestoreInstanceState(Parcelable state)4188     public void onRestoreInstanceState(Parcelable state) {
4189         if (!(state instanceof SavedState)) {
4190             super.onRestoreInstanceState(state);
4191             return;
4192         }
4193 
4194         SavedState ss = (SavedState)state;
4195         super.onRestoreInstanceState(ss.getSuperState());
4196 
4197         // XXX restore buffer type too, as well as lots of other stuff
4198         if (ss.text != null) {
4199             setText(ss.text);
4200         }
4201 
4202         if (ss.selStart >= 0 && ss.selEnd >= 0) {
4203             if (mText instanceof Spannable) {
4204                 int len = mText.length();
4205 
4206                 if (ss.selStart > len || ss.selEnd > len) {
4207                     String restored = "";
4208 
4209                     if (ss.text != null) {
4210                         restored = "(restored) ";
4211                     }
4212 
4213                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
4214                           "/" + ss.selEnd + " out of range for " + restored +
4215                           "text " + mText);
4216                 } else {
4217                     Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4218 
4219                     if (ss.frozenWithFocus) {
4220                         createEditorIfNeeded();
4221                         mEditor.mFrozenWithFocus = true;
4222                     }
4223                 }
4224             }
4225         }
4226 
4227         if (ss.error != null) {
4228             final CharSequence error = ss.error;
4229             // Display the error later, after the first layout pass
4230             post(new Runnable() {
4231                 public void run() {
4232                     if (mEditor == null || !mEditor.mErrorWasChanged) {
4233                         setError(error);
4234                     }
4235                 }
4236             });
4237         }
4238 
4239         if (ss.editorState != null) {
4240             createEditorIfNeeded();
4241             mEditor.restoreInstanceState(ss.editorState);
4242         }
4243     }
4244 
4245     /**
4246      * Control whether this text view saves its entire text contents when
4247      * freezing to an icicle, in addition to dynamic state such as cursor
4248      * position.  By default this is false, not saving the text.  Set to true
4249      * if the text in the text view is not being saved somewhere else in
4250      * persistent storage (such as in a content provider) so that if the
4251      * view is later thawed the user will not lose their data. For
4252      * {@link android.widget.EditText} it is always enabled, regardless of
4253      * the value of the attribute.
4254      *
4255      * @param freezesText Controls whether a frozen icicle should include the
4256      * entire text data: true to include it, false to not.
4257      *
4258      * @attr ref android.R.styleable#TextView_freezesText
4259      */
4260     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)4261     public void setFreezesText(boolean freezesText) {
4262         mFreezesText = freezesText;
4263     }
4264 
4265     /**
4266      * Return whether this text view is including its entire text contents
4267      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
4268      *
4269      * @return Returns true if text is included, false if it isn't.
4270      *
4271      * @see #setFreezesText
4272      */
getFreezesText()4273     public boolean getFreezesText() {
4274         return mFreezesText;
4275     }
4276 
4277     ///////////////////////////////////////////////////////////////////////////
4278 
4279     /**
4280      * Sets the Factory used to create new Editables.
4281      */
setEditableFactory(Editable.Factory factory)4282     public final void setEditableFactory(Editable.Factory factory) {
4283         mEditableFactory = factory;
4284         setText(mText);
4285     }
4286 
4287     /**
4288      * Sets the Factory used to create new Spannables.
4289      */
setSpannableFactory(Spannable.Factory factory)4290     public final void setSpannableFactory(Spannable.Factory factory) {
4291         mSpannableFactory = factory;
4292         setText(mText);
4293     }
4294 
4295     /**
4296      * Sets the string value of the TextView. TextView <em>does not</em> accept
4297      * HTML-like formatting, which you can do with text strings in XML resource files.
4298      * To style your strings, attach android.text.style.* objects to a
4299      * {@link android.text.SpannableString SpannableString}, or see the
4300      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
4301      * Available Resource Types</a> documentation for an example of setting
4302      * formatted text in the XML resource file.
4303      *
4304      * @attr ref android.R.styleable#TextView_text
4305      */
4306     @android.view.RemotableViewMethod
setText(CharSequence text)4307     public final void setText(CharSequence text) {
4308         setText(text, mBufferType);
4309     }
4310 
4311     /**
4312      * Like {@link #setText(CharSequence)},
4313      * except that the cursor position (if any) is retained in the new text.
4314      *
4315      * @param text The new text to place in the text view.
4316      *
4317      * @see #setText(CharSequence)
4318      */
4319     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)4320     public final void setTextKeepState(CharSequence text) {
4321         setTextKeepState(text, mBufferType);
4322     }
4323 
4324     /**
4325      * Sets the text that this TextView is to display (see
4326      * {@link #setText(CharSequence)}) and also sets whether it is stored
4327      * in a styleable/spannable buffer and whether it is editable.
4328      *
4329      * @attr ref android.R.styleable#TextView_text
4330      * @attr ref android.R.styleable#TextView_bufferType
4331      */
setText(CharSequence text, BufferType type)4332     public void setText(CharSequence text, BufferType type) {
4333         setText(text, type, true, 0);
4334 
4335         if (mCharWrapper != null) {
4336             mCharWrapper.mChars = null;
4337         }
4338     }
4339 
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)4340     private void setText(CharSequence text, BufferType type,
4341                          boolean notifyBefore, int oldlen) {
4342         if (text == null) {
4343             text = "";
4344         }
4345 
4346         // If suggestions are not enabled, remove the suggestion spans from the text
4347         if (!isSuggestionsEnabled()) {
4348             text = removeSuggestionSpans(text);
4349         }
4350 
4351         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4352 
4353         if (text instanceof Spanned &&
4354             ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
4355             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4356                 setHorizontalFadingEdgeEnabled(true);
4357                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4358             } else {
4359                 setHorizontalFadingEdgeEnabled(false);
4360                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4361             }
4362             setEllipsize(TextUtils.TruncateAt.MARQUEE);
4363         }
4364 
4365         int n = mFilters.length;
4366         for (int i = 0; i < n; i++) {
4367             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
4368             if (out != null) {
4369                 text = out;
4370             }
4371         }
4372 
4373         if (notifyBefore) {
4374             if (mText != null) {
4375                 oldlen = mText.length();
4376                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4377             } else {
4378                 sendBeforeTextChanged("", 0, 0, text.length());
4379             }
4380         }
4381 
4382         boolean needEditableForNotification = false;
4383 
4384         if (mListeners != null && mListeners.size() != 0) {
4385             needEditableForNotification = true;
4386         }
4387 
4388         if (type == BufferType.EDITABLE || getKeyListener() != null ||
4389                 needEditableForNotification) {
4390             createEditorIfNeeded();
4391             mEditor.forgetUndoRedo();
4392             Editable t = mEditableFactory.newEditable(text);
4393             text = t;
4394             setFilters(t, mFilters);
4395             InputMethodManager imm = InputMethodManager.peekInstance();
4396             if (imm != null) imm.restartInput(this);
4397         } else if (type == BufferType.SPANNABLE || mMovement != null) {
4398             text = mSpannableFactory.newSpannable(text);
4399         } else if (!(text instanceof CharWrapper)) {
4400             text = TextUtils.stringOrSpannedString(text);
4401         }
4402 
4403         if (mAutoLinkMask != 0) {
4404             Spannable s2;
4405 
4406             if (type == BufferType.EDITABLE || text instanceof Spannable) {
4407                 s2 = (Spannable) text;
4408             } else {
4409                 s2 = mSpannableFactory.newSpannable(text);
4410             }
4411 
4412             if (Linkify.addLinks(s2, mAutoLinkMask)) {
4413                 text = s2;
4414                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4415 
4416                 /*
4417                  * We must go ahead and set the text before changing the
4418                  * movement method, because setMovementMethod() may call
4419                  * setText() again to try to upgrade the buffer type.
4420                  */
4421                 mText = text;
4422 
4423                 // Do not change the movement method for text that support text selection as it
4424                 // would prevent an arbitrary cursor displacement.
4425                 if (mLinksClickable && !textCanBeSelected()) {
4426                     setMovementMethod(LinkMovementMethod.getInstance());
4427                 }
4428             }
4429         }
4430 
4431         mBufferType = type;
4432         mText = text;
4433 
4434         if (mTransformation == null) {
4435             mTransformed = text;
4436         } else {
4437             mTransformed = mTransformation.getTransformation(text, this);
4438         }
4439 
4440         final int textLength = text.length();
4441 
4442         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4443             Spannable sp = (Spannable) text;
4444 
4445             // Remove any ChangeWatchers that might have come from other TextViews.
4446             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4447             final int count = watchers.length;
4448             for (int i = 0; i < count; i++) {
4449                 sp.removeSpan(watchers[i]);
4450             }
4451 
4452             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4453 
4454             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4455                        (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4456 
4457             if (mEditor != null) mEditor.addSpanWatchers(sp);
4458 
4459             if (mTransformation != null) {
4460                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4461             }
4462 
4463             if (mMovement != null) {
4464                 mMovement.initialize(this, (Spannable) text);
4465 
4466                 /*
4467                  * Initializing the movement method will have set the
4468                  * selection, so reset mSelectionMoved to keep that from
4469                  * interfering with the normal on-focus selection-setting.
4470                  */
4471                 if (mEditor != null) mEditor.mSelectionMoved = false;
4472             }
4473         }
4474 
4475         if (mLayout != null) {
4476             checkForRelayout();
4477         }
4478 
4479         sendOnTextChanged(text, 0, oldlen, textLength);
4480         onTextChanged(text, 0, oldlen, textLength);
4481 
4482         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4483 
4484         if (needEditableForNotification) {
4485             sendAfterTextChanged((Editable) text);
4486         }
4487 
4488         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4489         if (mEditor != null) mEditor.prepareCursorControllers();
4490     }
4491 
4492     /**
4493      * Sets the TextView to display the specified slice of the specified
4494      * char array.  You must promise that you will not change the contents
4495      * of the array except for right before another call to setText(),
4496      * since the TextView has no way to know that the text
4497      * has changed and that it needs to invalidate and re-layout.
4498      */
setText(char[] text, int start, int len)4499     public final void setText(char[] text, int start, int len) {
4500         int oldlen = 0;
4501 
4502         if (start < 0 || len < 0 || start + len > text.length) {
4503             throw new IndexOutOfBoundsException(start + ", " + len);
4504         }
4505 
4506         /*
4507          * We must do the before-notification here ourselves because if
4508          * the old text is a CharWrapper we destroy it before calling
4509          * into the normal path.
4510          */
4511         if (mText != null) {
4512             oldlen = mText.length();
4513             sendBeforeTextChanged(mText, 0, oldlen, len);
4514         } else {
4515             sendBeforeTextChanged("", 0, 0, len);
4516         }
4517 
4518         if (mCharWrapper == null) {
4519             mCharWrapper = new CharWrapper(text, start, len);
4520         } else {
4521             mCharWrapper.set(text, start, len);
4522         }
4523 
4524         setText(mCharWrapper, mBufferType, false, oldlen);
4525     }
4526 
4527     /**
4528      * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4529      * except that the cursor position (if any) is retained in the new text.
4530      *
4531      * @see #setText(CharSequence, android.widget.TextView.BufferType)
4532      */
setTextKeepState(CharSequence text, BufferType type)4533     public final void setTextKeepState(CharSequence text, BufferType type) {
4534         int start = getSelectionStart();
4535         int end = getSelectionEnd();
4536         int len = text.length();
4537 
4538         setText(text, type);
4539 
4540         if (start >= 0 || end >= 0) {
4541             if (mText instanceof Spannable) {
4542                 Selection.setSelection((Spannable) mText,
4543                                        Math.max(0, Math.min(start, len)),
4544                                        Math.max(0, Math.min(end, len)));
4545             }
4546         }
4547     }
4548 
4549     @android.view.RemotableViewMethod
setText(@tringRes int resid)4550     public final void setText(@StringRes int resid) {
4551         setText(getContext().getResources().getText(resid));
4552     }
4553 
setText(@tringRes int resid, BufferType type)4554     public final void setText(@StringRes int resid, BufferType type) {
4555         setText(getContext().getResources().getText(resid), type);
4556     }
4557 
4558     /**
4559      * Sets the text to be displayed when the text of the TextView is empty.
4560      * Null means to use the normal empty text. The hint does not currently
4561      * participate in determining the size of the view.
4562      *
4563      * @attr ref android.R.styleable#TextView_hint
4564      */
4565     @android.view.RemotableViewMethod
setHint(CharSequence hint)4566     public final void setHint(CharSequence hint) {
4567         mHint = TextUtils.stringOrSpannedString(hint);
4568 
4569         if (mLayout != null) {
4570             checkForRelayout();
4571         }
4572 
4573         if (mText.length() == 0) {
4574             invalidate();
4575         }
4576 
4577         // Invalidate display list if hint is currently used
4578         if (mEditor != null && mText.length() == 0 && mHint != null) {
4579             mEditor.invalidateTextDisplayList();
4580         }
4581     }
4582 
4583     /**
4584      * Sets the text to be displayed when the text of the TextView is empty,
4585      * from a resource.
4586      *
4587      * @attr ref android.R.styleable#TextView_hint
4588      */
4589     @android.view.RemotableViewMethod
setHint(@tringRes int resid)4590     public final void setHint(@StringRes int resid) {
4591         setHint(getContext().getResources().getText(resid));
4592     }
4593 
4594     /**
4595      * Returns the hint that is displayed when the text of the TextView
4596      * is empty.
4597      *
4598      * @attr ref android.R.styleable#TextView_hint
4599      */
4600     @ViewDebug.CapturedViewProperty
getHint()4601     public CharSequence getHint() {
4602         return mHint;
4603     }
4604 
isSingleLine()4605     boolean isSingleLine() {
4606         return mSingleLine;
4607     }
4608 
isMultilineInputType(int type)4609     private static boolean isMultilineInputType(int type) {
4610         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4611             (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4612     }
4613 
4614     /**
4615      * Removes the suggestion spans.
4616      */
removeSuggestionSpans(CharSequence text)4617     CharSequence removeSuggestionSpans(CharSequence text) {
4618        if (text instanceof Spanned) {
4619            Spannable spannable;
4620            if (text instanceof Spannable) {
4621                spannable = (Spannable) text;
4622            } else {
4623                spannable = new SpannableString(text);
4624                text = spannable;
4625            }
4626 
4627            SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4628            for (int i = 0; i < spans.length; i++) {
4629                spannable.removeSpan(spans[i]);
4630            }
4631        }
4632        return text;
4633     }
4634 
4635     /**
4636      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4637      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4638      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
4639      * then a soft keyboard will not be displayed for this text view.
4640      *
4641      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4642      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4643      * type.
4644      *
4645      * @see #getInputType()
4646      * @see #setRawInputType(int)
4647      * @see android.text.InputType
4648      * @attr ref android.R.styleable#TextView_inputType
4649      */
setInputType(int type)4650     public void setInputType(int type) {
4651         final boolean wasPassword = isPasswordInputType(getInputType());
4652         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4653         setInputType(type, false);
4654         final boolean isPassword = isPasswordInputType(type);
4655         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4656         boolean forceUpdate = false;
4657         if (isPassword) {
4658             setTransformationMethod(PasswordTransformationMethod.getInstance());
4659             setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4660         } else if (isVisiblePassword) {
4661             if (mTransformation == PasswordTransformationMethod.getInstance()) {
4662                 forceUpdate = true;
4663             }
4664             setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4665         } else if (wasPassword || wasVisiblePassword) {
4666             // not in password mode, clean up typeface and transformation
4667             setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4668             if (mTransformation == PasswordTransformationMethod.getInstance()) {
4669                 forceUpdate = true;
4670             }
4671         }
4672 
4673         boolean singleLine = !isMultilineInputType(type);
4674 
4675         // We need to update the single line mode if it has changed or we
4676         // were previously in password mode.
4677         if (mSingleLine != singleLine || forceUpdate) {
4678             // Change single line mode, but only change the transformation if
4679             // we are not in password mode.
4680             applySingleLine(singleLine, !isPassword, true);
4681         }
4682 
4683         if (!isSuggestionsEnabled()) {
4684             mText = removeSuggestionSpans(mText);
4685         }
4686 
4687         InputMethodManager imm = InputMethodManager.peekInstance();
4688         if (imm != null) imm.restartInput(this);
4689     }
4690 
4691     /**
4692      * It would be better to rely on the input type for everything. A password inputType should have
4693      * a password transformation. We should hence use isPasswordInputType instead of this method.
4694      *
4695      * We should:
4696      * - Call setInputType in setKeyListener instead of changing the input type directly (which
4697      * would install the correct transformation).
4698      * - Refuse the installation of a non-password transformation in setTransformation if the input
4699      * type is password.
4700      *
4701      * However, this is like this for legacy reasons and we cannot break existing apps. This method
4702      * is useful since it matches what the user can see (obfuscated text or not).
4703      *
4704      * @return true if the current transformation method is of the password type.
4705      */
hasPasswordTransformationMethod()4706     boolean hasPasswordTransformationMethod() {
4707         return mTransformation instanceof PasswordTransformationMethod;
4708     }
4709 
isPasswordInputType(int inputType)4710     private static boolean isPasswordInputType(int inputType) {
4711         final int variation =
4712                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4713         return variation
4714                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4715                 || variation
4716                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4717                 || variation
4718                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4719     }
4720 
isVisiblePasswordInputType(int inputType)4721     private static boolean isVisiblePasswordInputType(int inputType) {
4722         final int variation =
4723                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4724         return variation
4725                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4726     }
4727 
4728     /**
4729      * Directly change the content type integer of the text view, without
4730      * modifying any other state.
4731      * @see #setInputType(int)
4732      * @see android.text.InputType
4733      * @attr ref android.R.styleable#TextView_inputType
4734      */
setRawInputType(int type)4735     public void setRawInputType(int type) {
4736         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4737         createEditorIfNeeded();
4738         mEditor.mInputType = type;
4739     }
4740 
setInputType(int type, boolean direct)4741     private void setInputType(int type, boolean direct) {
4742         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4743         KeyListener input;
4744         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4745             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4746             TextKeyListener.Capitalize cap;
4747             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4748                 cap = TextKeyListener.Capitalize.CHARACTERS;
4749             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4750                 cap = TextKeyListener.Capitalize.WORDS;
4751             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4752                 cap = TextKeyListener.Capitalize.SENTENCES;
4753             } else {
4754                 cap = TextKeyListener.Capitalize.NONE;
4755             }
4756             input = TextKeyListener.getInstance(autotext, cap);
4757         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4758             input = DigitsKeyListener.getInstance(
4759                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4760                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4761         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4762             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4763                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4764                     input = DateKeyListener.getInstance();
4765                     break;
4766                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4767                     input = TimeKeyListener.getInstance();
4768                     break;
4769                 default:
4770                     input = DateTimeKeyListener.getInstance();
4771                     break;
4772             }
4773         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4774             input = DialerKeyListener.getInstance();
4775         } else {
4776             input = TextKeyListener.getInstance();
4777         }
4778         setRawInputType(type);
4779         if (direct) {
4780             createEditorIfNeeded();
4781             mEditor.mKeyListener = input;
4782         } else {
4783             setKeyListenerOnly(input);
4784         }
4785     }
4786 
4787     /**
4788      * Get the type of the editable content.
4789      *
4790      * @see #setInputType(int)
4791      * @see android.text.InputType
4792      */
getInputType()4793     public int getInputType() {
4794         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4795     }
4796 
4797     /**
4798      * Change the editor type integer associated with the text view, which
4799      * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4800      * has focus.
4801      * @see #getImeOptions
4802      * @see android.view.inputmethod.EditorInfo
4803      * @attr ref android.R.styleable#TextView_imeOptions
4804      */
setImeOptions(int imeOptions)4805     public void setImeOptions(int imeOptions) {
4806         createEditorIfNeeded();
4807         mEditor.createInputContentTypeIfNeeded();
4808         mEditor.mInputContentType.imeOptions = imeOptions;
4809     }
4810 
4811     /**
4812      * Get the type of the IME editor.
4813      *
4814      * @see #setImeOptions(int)
4815      * @see android.view.inputmethod.EditorInfo
4816      */
getImeOptions()4817     public int getImeOptions() {
4818         return mEditor != null && mEditor.mInputContentType != null
4819                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4820     }
4821 
4822     /**
4823      * Change the custom IME action associated with the text view, which
4824      * will be reported to an IME with {@link EditorInfo#actionLabel}
4825      * and {@link EditorInfo#actionId} when it has focus.
4826      * @see #getImeActionLabel
4827      * @see #getImeActionId
4828      * @see android.view.inputmethod.EditorInfo
4829      * @attr ref android.R.styleable#TextView_imeActionLabel
4830      * @attr ref android.R.styleable#TextView_imeActionId
4831      */
setImeActionLabel(CharSequence label, int actionId)4832     public void setImeActionLabel(CharSequence label, int actionId) {
4833         createEditorIfNeeded();
4834         mEditor.createInputContentTypeIfNeeded();
4835         mEditor.mInputContentType.imeActionLabel = label;
4836         mEditor.mInputContentType.imeActionId = actionId;
4837     }
4838 
4839     /**
4840      * Get the IME action label previous set with {@link #setImeActionLabel}.
4841      *
4842      * @see #setImeActionLabel
4843      * @see android.view.inputmethod.EditorInfo
4844      */
getImeActionLabel()4845     public CharSequence getImeActionLabel() {
4846         return mEditor != null && mEditor.mInputContentType != null
4847                 ? mEditor.mInputContentType.imeActionLabel : null;
4848     }
4849 
4850     /**
4851      * Get the IME action ID previous set with {@link #setImeActionLabel}.
4852      *
4853      * @see #setImeActionLabel
4854      * @see android.view.inputmethod.EditorInfo
4855      */
getImeActionId()4856     public int getImeActionId() {
4857         return mEditor != null && mEditor.mInputContentType != null
4858                 ? mEditor.mInputContentType.imeActionId : 0;
4859     }
4860 
4861     /**
4862      * Set a special listener to be called when an action is performed
4863      * on the text view.  This will be called when the enter key is pressed,
4864      * or when an action supplied to the IME is selected by the user.  Setting
4865      * this means that the normal hard key event will not insert a newline
4866      * into the text view, even if it is multi-line; holding down the ALT
4867      * modifier will, however, allow the user to insert a newline character.
4868      */
setOnEditorActionListener(OnEditorActionListener l)4869     public void setOnEditorActionListener(OnEditorActionListener l) {
4870         createEditorIfNeeded();
4871         mEditor.createInputContentTypeIfNeeded();
4872         mEditor.mInputContentType.onEditorActionListener = l;
4873     }
4874 
4875     /**
4876      * Called when an attached input method calls
4877      * {@link InputConnection#performEditorAction(int)
4878      * InputConnection.performEditorAction()}
4879      * for this text view.  The default implementation will call your action
4880      * listener supplied to {@link #setOnEditorActionListener}, or perform
4881      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4882      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4883      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4884      * EditorInfo.IME_ACTION_DONE}.
4885      *
4886      * <p>For backwards compatibility, if no IME options have been set and the
4887      * text view would not normally advance focus on enter, then
4888      * the NEXT and DONE actions received here will be turned into an enter
4889      * key down/up pair to go through the normal key handling.
4890      *
4891      * @param actionCode The code of the action being performed.
4892      *
4893      * @see #setOnEditorActionListener
4894      */
onEditorAction(int actionCode)4895     public void onEditorAction(int actionCode) {
4896         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4897         if (ict != null) {
4898             if (ict.onEditorActionListener != null) {
4899                 if (ict.onEditorActionListener.onEditorAction(this,
4900                         actionCode, null)) {
4901                     return;
4902                 }
4903             }
4904 
4905             // This is the handling for some default action.
4906             // Note that for backwards compatibility we don't do this
4907             // default handling if explicit ime options have not been given,
4908             // instead turning this into the normal enter key codes that an
4909             // app may be expecting.
4910             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4911                 View v = focusSearch(FOCUS_FORWARD);
4912                 if (v != null) {
4913                     if (!v.requestFocus(FOCUS_FORWARD)) {
4914                         throw new IllegalStateException("focus search returned a view " +
4915                                 "that wasn't able to take focus!");
4916                     }
4917                 }
4918                 return;
4919 
4920             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4921                 View v = focusSearch(FOCUS_BACKWARD);
4922                 if (v != null) {
4923                     if (!v.requestFocus(FOCUS_BACKWARD)) {
4924                         throw new IllegalStateException("focus search returned a view " +
4925                                 "that wasn't able to take focus!");
4926                     }
4927                 }
4928                 return;
4929 
4930             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4931                 InputMethodManager imm = InputMethodManager.peekInstance();
4932                 if (imm != null && imm.isActive(this)) {
4933                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
4934                 }
4935                 return;
4936             }
4937         }
4938 
4939         ViewRootImpl viewRootImpl = getViewRootImpl();
4940         if (viewRootImpl != null) {
4941             long eventTime = SystemClock.uptimeMillis();
4942             viewRootImpl.dispatchKeyFromIme(
4943                     new KeyEvent(eventTime, eventTime,
4944                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4945                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4946                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4947                     | KeyEvent.FLAG_EDITOR_ACTION));
4948             viewRootImpl.dispatchKeyFromIme(
4949                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4950                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4951                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4952                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4953                     | KeyEvent.FLAG_EDITOR_ACTION));
4954         }
4955     }
4956 
4957     /**
4958      * Set the private content type of the text, which is the
4959      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4960      * field that will be filled in when creating an input connection.
4961      *
4962      * @see #getPrivateImeOptions()
4963      * @see EditorInfo#privateImeOptions
4964      * @attr ref android.R.styleable#TextView_privateImeOptions
4965      */
setPrivateImeOptions(String type)4966     public void setPrivateImeOptions(String type) {
4967         createEditorIfNeeded();
4968         mEditor.createInputContentTypeIfNeeded();
4969         mEditor.mInputContentType.privateImeOptions = type;
4970     }
4971 
4972     /**
4973      * Get the private type of the content.
4974      *
4975      * @see #setPrivateImeOptions(String)
4976      * @see EditorInfo#privateImeOptions
4977      */
getPrivateImeOptions()4978     public String getPrivateImeOptions() {
4979         return mEditor != null && mEditor.mInputContentType != null
4980                 ? mEditor.mInputContentType.privateImeOptions : null;
4981     }
4982 
4983     /**
4984      * Set the extra input data of the text, which is the
4985      * {@link EditorInfo#extras TextBoxAttribute.extras}
4986      * Bundle that will be filled in when creating an input connection.  The
4987      * given integer is the resource ID of an XML resource holding an
4988      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4989      *
4990      * @see #getInputExtras(boolean)
4991      * @see EditorInfo#extras
4992      * @attr ref android.R.styleable#TextView_editorExtras
4993      */
setInputExtras(@mlRes int xmlResId)4994     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
4995         createEditorIfNeeded();
4996         XmlResourceParser parser = getResources().getXml(xmlResId);
4997         mEditor.createInputContentTypeIfNeeded();
4998         mEditor.mInputContentType.extras = new Bundle();
4999         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
5000     }
5001 
5002     /**
5003      * Retrieve the input extras currently associated with the text view, which
5004      * can be viewed as well as modified.
5005      *
5006      * @param create If true, the extras will be created if they don't already
5007      * exist.  Otherwise, null will be returned if none have been created.
5008      * @see #setInputExtras(int)
5009      * @see EditorInfo#extras
5010      * @attr ref android.R.styleable#TextView_editorExtras
5011      */
getInputExtras(boolean create)5012     public Bundle getInputExtras(boolean create) {
5013         if (mEditor == null && !create) return null;
5014         createEditorIfNeeded();
5015         if (mEditor.mInputContentType == null) {
5016             if (!create) return null;
5017             mEditor.createInputContentTypeIfNeeded();
5018         }
5019         if (mEditor.mInputContentType.extras == null) {
5020             if (!create) return null;
5021             mEditor.mInputContentType.extras = new Bundle();
5022         }
5023         return mEditor.mInputContentType.extras;
5024     }
5025 
5026     /**
5027      * Change "hint" locales associated with the text view, which will be reported to an IME with
5028      * {@link EditorInfo#hintLocales} when it has focus.
5029      *
5030      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
5031      * call {@link InputMethodManager#restartInput(View)}.</p>
5032      * @param hintLocales List of the languages that the user is supposed to switch to no matter
5033      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
5034      * @see #getImeHIntLocales()
5035      * @see android.view.inputmethod.EditorInfo#hintLocales
5036      */
setImeHintLocales(@ullable LocaleList hintLocales)5037     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
5038         createEditorIfNeeded();
5039         mEditor.createInputContentTypeIfNeeded();
5040         mEditor.mInputContentType.imeHintLocales = hintLocales;
5041     }
5042 
5043     /**
5044      * @return The current languages list "hint". {@code null} when no "hint" is available.
5045      * @see #setImeHintLocales(LocaleList)
5046      * @see android.view.inputmethod.EditorInfo#hintLocales
5047      */
5048     @Nullable
getImeHintLocales()5049     public LocaleList getImeHintLocales() {
5050         if (mEditor == null) { return null; }
5051         if (mEditor.mInputContentType == null) { return null; }
5052         return mEditor.mInputContentType.imeHintLocales;
5053     }
5054 
5055     /**
5056      * Returns the error message that was set to be displayed with
5057      * {@link #setError}, or <code>null</code> if no error was set
5058      * or if it the error was cleared by the widget after user input.
5059      */
getError()5060     public CharSequence getError() {
5061         return mEditor == null ? null : mEditor.mError;
5062     }
5063 
5064     /**
5065      * Sets the right-hand compound drawable of the TextView to the "error"
5066      * icon and sets an error message that will be displayed in a popup when
5067      * the TextView has focus.  The icon and error message will be reset to
5068      * null when any key events cause changes to the TextView's text.  If the
5069      * <code>error</code> is <code>null</code>, the error message and icon
5070      * will be cleared.
5071      */
5072     @android.view.RemotableViewMethod
setError(CharSequence error)5073     public void setError(CharSequence error) {
5074         if (error == null) {
5075             setError(null, null);
5076         } else {
5077             Drawable dr = getContext().getDrawable(
5078                     com.android.internal.R.drawable.indicator_input_error);
5079 
5080             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5081             setError(error, dr);
5082         }
5083     }
5084 
5085     /**
5086      * Sets the right-hand compound drawable of the TextView to the specified
5087      * icon and sets an error message that will be displayed in a popup when
5088      * the TextView has focus.  The icon and error message will be reset to
5089      * null when any key events cause changes to the TextView's text.  The
5090      * drawable must already have had {@link Drawable#setBounds} set on it.
5091      * If the <code>error</code> is <code>null</code>, the error message will
5092      * be cleared (and you should provide a <code>null</code> icon as well).
5093      */
setError(CharSequence error, Drawable icon)5094     public void setError(CharSequence error, Drawable icon) {
5095         createEditorIfNeeded();
5096         mEditor.setError(error, icon);
5097         notifyViewAccessibilityStateChangedIfNeeded(
5098                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
5099     }
5100 
5101     @Override
setFrame(int l, int t, int r, int b)5102     protected boolean setFrame(int l, int t, int r, int b) {
5103         boolean result = super.setFrame(l, t, r, b);
5104 
5105         if (mEditor != null) mEditor.setFrame();
5106 
5107         restartMarqueeIfNeeded();
5108 
5109         return result;
5110     }
5111 
restartMarqueeIfNeeded()5112     private void restartMarqueeIfNeeded() {
5113         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5114             mRestartMarquee = false;
5115             startMarquee();
5116         }
5117     }
5118 
5119     /**
5120      * Sets the list of input filters that will be used if the buffer is
5121      * Editable. Has no effect otherwise.
5122      *
5123      * @attr ref android.R.styleable#TextView_maxLength
5124      */
setFilters(InputFilter[] filters)5125     public void setFilters(InputFilter[] filters) {
5126         if (filters == null) {
5127             throw new IllegalArgumentException();
5128         }
5129 
5130         mFilters = filters;
5131 
5132         if (mText instanceof Editable) {
5133             setFilters((Editable) mText, filters);
5134         }
5135     }
5136 
5137     /**
5138      * Sets the list of input filters on the specified Editable,
5139      * and includes mInput in the list if it is an InputFilter.
5140      */
setFilters(Editable e, InputFilter[] filters)5141     private void setFilters(Editable e, InputFilter[] filters) {
5142         if (mEditor != null) {
5143             final boolean undoFilter = mEditor.mUndoInputFilter != null;
5144             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5145             int num = 0;
5146             if (undoFilter) num++;
5147             if (keyFilter) num++;
5148             if (num > 0) {
5149                 InputFilter[] nf = new InputFilter[filters.length + num];
5150 
5151                 System.arraycopy(filters, 0, nf, 0, filters.length);
5152                 num = 0;
5153                 if (undoFilter) {
5154                     nf[filters.length] = mEditor.mUndoInputFilter;
5155                     num++;
5156                 }
5157                 if (keyFilter) {
5158                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5159                 }
5160 
5161                 e.setFilters(nf);
5162                 return;
5163             }
5164         }
5165         e.setFilters(filters);
5166     }
5167 
5168     /**
5169      * Returns the current list of input filters.
5170      *
5171      * @attr ref android.R.styleable#TextView_maxLength
5172      */
getFilters()5173     public InputFilter[] getFilters() {
5174         return mFilters;
5175     }
5176 
5177     /////////////////////////////////////////////////////////////////////////
5178 
getBoxHeight(Layout l)5179     private int getBoxHeight(Layout l) {
5180         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5181         int padding = (l == mHintLayout) ?
5182                 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5183                 getExtendedPaddingTop() + getExtendedPaddingBottom();
5184         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5185     }
5186 
getVerticalOffset(boolean forceNormal)5187     int getVerticalOffset(boolean forceNormal) {
5188         int voffset = 0;
5189         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5190 
5191         Layout l = mLayout;
5192         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5193             l = mHintLayout;
5194         }
5195 
5196         if (gravity != Gravity.TOP) {
5197             int boxht = getBoxHeight(l);
5198             int textht = l.getHeight();
5199 
5200             if (textht < boxht) {
5201                 if (gravity == Gravity.BOTTOM)
5202                     voffset = boxht - textht;
5203                 else // (gravity == Gravity.CENTER_VERTICAL)
5204                     voffset = (boxht - textht) >> 1;
5205             }
5206         }
5207         return voffset;
5208     }
5209 
getBottomVerticalOffset(boolean forceNormal)5210     private int getBottomVerticalOffset(boolean forceNormal) {
5211         int voffset = 0;
5212         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5213 
5214         Layout l = mLayout;
5215         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5216             l = mHintLayout;
5217         }
5218 
5219         if (gravity != Gravity.BOTTOM) {
5220             int boxht = getBoxHeight(l);
5221             int textht = l.getHeight();
5222 
5223             if (textht < boxht) {
5224                 if (gravity == Gravity.TOP)
5225                     voffset = boxht - textht;
5226                 else // (gravity == Gravity.CENTER_VERTICAL)
5227                     voffset = (boxht - textht) >> 1;
5228             }
5229         }
5230         return voffset;
5231     }
5232 
invalidateCursorPath()5233     void invalidateCursorPath() {
5234         if (mHighlightPathBogus) {
5235             invalidateCursor();
5236         } else {
5237             final int horizontalPadding = getCompoundPaddingLeft();
5238             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5239 
5240             if (mEditor.mCursorCount == 0) {
5241                 synchronized (TEMP_RECTF) {
5242                     /*
5243                      * The reason for this concern about the thickness of the
5244                      * cursor and doing the floor/ceil on the coordinates is that
5245                      * some EditTexts (notably textfields in the Browser) have
5246                      * anti-aliased text where not all the characters are
5247                      * necessarily at integer-multiple locations.  This should
5248                      * make sure the entire cursor gets invalidated instead of
5249                      * sometimes missing half a pixel.
5250                      */
5251                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
5252                     if (thick < 1.0f) {
5253                         thick = 1.0f;
5254                     }
5255 
5256                     thick /= 2.0f;
5257 
5258                     // mHighlightPath is guaranteed to be non null at that point.
5259                     mHighlightPath.computeBounds(TEMP_RECTF, false);
5260 
5261                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5262                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5263                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5264                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
5265                 }
5266             } else {
5267                 for (int i = 0; i < mEditor.mCursorCount; i++) {
5268                     Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5269                     invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5270                             bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5271                 }
5272             }
5273         }
5274     }
5275 
invalidateCursor()5276     void invalidateCursor() {
5277         int where = getSelectionEnd();
5278 
5279         invalidateCursor(where, where, where);
5280     }
5281 
invalidateCursor(int a, int b, int c)5282     private void invalidateCursor(int a, int b, int c) {
5283         if (a >= 0 || b >= 0 || c >= 0) {
5284             int start = Math.min(Math.min(a, b), c);
5285             int end = Math.max(Math.max(a, b), c);
5286             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
5287         }
5288     }
5289 
5290     /**
5291      * Invalidates the region of text enclosed between the start and end text offsets.
5292      */
invalidateRegion(int start, int end, boolean invalidateCursor)5293     void invalidateRegion(int start, int end, boolean invalidateCursor) {
5294         if (mLayout == null) {
5295             invalidate();
5296         } else {
5297                 int lineStart = mLayout.getLineForOffset(start);
5298                 int top = mLayout.getLineTop(lineStart);
5299 
5300                 // This is ridiculous, but the descent from the line above
5301                 // can hang down into the line we really want to redraw,
5302                 // so we have to invalidate part of the line above to make
5303                 // sure everything that needs to be redrawn really is.
5304                 // (But not the whole line above, because that would cause
5305                 // the same problem with the descenders on the line above it!)
5306                 if (lineStart > 0) {
5307                     top -= mLayout.getLineDescent(lineStart - 1);
5308                 }
5309 
5310                 int lineEnd;
5311 
5312                 if (start == end)
5313                     lineEnd = lineStart;
5314                 else
5315                     lineEnd = mLayout.getLineForOffset(end);
5316 
5317                 int bottom = mLayout.getLineBottom(lineEnd);
5318 
5319                 // mEditor can be null in case selection is set programmatically.
5320                 if (invalidateCursor && mEditor != null) {
5321                     for (int i = 0; i < mEditor.mCursorCount; i++) {
5322                         Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5323                         top = Math.min(top, bounds.top);
5324                         bottom = Math.max(bottom, bounds.bottom);
5325                     }
5326                 }
5327 
5328                 final int compoundPaddingLeft = getCompoundPaddingLeft();
5329                 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5330 
5331                 int left, right;
5332                 if (lineStart == lineEnd && !invalidateCursor) {
5333                     left = (int) mLayout.getPrimaryHorizontal(start);
5334                     right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5335                     left += compoundPaddingLeft;
5336                     right += compoundPaddingLeft;
5337                 } else {
5338                     // Rectangle bounding box when the region spans several lines
5339                     left = compoundPaddingLeft;
5340                     right = getWidth() - getCompoundPaddingRight();
5341                 }
5342 
5343                 invalidate(mScrollX + left, verticalPadding + top,
5344                         mScrollX + right, verticalPadding + bottom);
5345         }
5346     }
5347 
registerForPreDraw()5348     private void registerForPreDraw() {
5349         if (!mPreDrawRegistered) {
5350             getViewTreeObserver().addOnPreDrawListener(this);
5351             mPreDrawRegistered = true;
5352         }
5353     }
5354 
unregisterForPreDraw()5355     private void unregisterForPreDraw() {
5356         getViewTreeObserver().removeOnPreDrawListener(this);
5357         mPreDrawRegistered = false;
5358         mPreDrawListenerDetached = false;
5359     }
5360 
5361     /**
5362      * {@inheritDoc}
5363      */
onPreDraw()5364     public boolean onPreDraw() {
5365         if (mLayout == null) {
5366             assumeLayout();
5367         }
5368 
5369         if (mMovement != null) {
5370             /* This code also provides auto-scrolling when a cursor is moved using a
5371              * CursorController (insertion point or selection limits).
5372              * For selection, ensure start or end is visible depending on controller's state.
5373              */
5374             int curs = getSelectionEnd();
5375             // Do not create the controller if it is not already created.
5376             if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5377                     mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
5378                 curs = getSelectionStart();
5379             }
5380 
5381             /*
5382              * TODO: This should really only keep the end in view if
5383              * it already was before the text changed.  I'm not sure
5384              * of a good way to tell from here if it was.
5385              */
5386             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5387                 curs = mText.length();
5388             }
5389 
5390             if (curs >= 0) {
5391                 bringPointIntoView(curs);
5392             }
5393         } else {
5394             bringTextIntoView();
5395         }
5396 
5397         // This has to be checked here since:
5398         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5399         //   a screen rotation) since layout is not yet initialized at that point.
5400         if (mEditor != null && mEditor.mCreatedWithASelection) {
5401             mEditor.refreshTextActionMode();
5402             mEditor.mCreatedWithASelection = false;
5403         }
5404 
5405         unregisterForPreDraw();
5406 
5407         return true;
5408     }
5409 
5410     @Override
onAttachedToWindow()5411     protected void onAttachedToWindow() {
5412         super.onAttachedToWindow();
5413 
5414         if (mEditor != null) mEditor.onAttachedToWindow();
5415 
5416         if (mPreDrawListenerDetached) {
5417             getViewTreeObserver().addOnPreDrawListener(this);
5418             mPreDrawListenerDetached = false;
5419         }
5420     }
5421 
5422     /** @hide */
5423     @Override
onDetachedFromWindowInternal()5424     protected void onDetachedFromWindowInternal() {
5425         if (mPreDrawRegistered) {
5426             getViewTreeObserver().removeOnPreDrawListener(this);
5427             mPreDrawListenerDetached = true;
5428         }
5429 
5430         resetResolvedDrawables();
5431 
5432         if (mEditor != null) mEditor.onDetachedFromWindow();
5433 
5434         super.onDetachedFromWindowInternal();
5435     }
5436 
5437     @Override
onScreenStateChanged(int screenState)5438     public void onScreenStateChanged(int screenState) {
5439         super.onScreenStateChanged(screenState);
5440         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5441     }
5442 
5443     @Override
isPaddingOffsetRequired()5444     protected boolean isPaddingOffsetRequired() {
5445         return mShadowRadius != 0 || mDrawables != null;
5446     }
5447 
5448     @Override
getLeftPaddingOffset()5449     protected int getLeftPaddingOffset() {
5450         return getCompoundPaddingLeft() - mPaddingLeft +
5451                 (int) Math.min(0, mShadowDx - mShadowRadius);
5452     }
5453 
5454     @Override
getTopPaddingOffset()5455     protected int getTopPaddingOffset() {
5456         return (int) Math.min(0, mShadowDy - mShadowRadius);
5457     }
5458 
5459     @Override
getBottomPaddingOffset()5460     protected int getBottomPaddingOffset() {
5461         return (int) Math.max(0, mShadowDy + mShadowRadius);
5462     }
5463 
5464     @Override
getRightPaddingOffset()5465     protected int getRightPaddingOffset() {
5466         return -(getCompoundPaddingRight() - mPaddingRight) +
5467                 (int) Math.max(0, mShadowDx + mShadowRadius);
5468     }
5469 
5470     @Override
verifyDrawable(@onNull Drawable who)5471     protected boolean verifyDrawable(@NonNull Drawable who) {
5472         final boolean verified = super.verifyDrawable(who);
5473         if (!verified && mDrawables != null) {
5474             for (Drawable dr : mDrawables.mShowing) {
5475                 if (who == dr) {
5476                     return true;
5477                 }
5478             }
5479         }
5480         return verified;
5481     }
5482 
5483     @Override
jumpDrawablesToCurrentState()5484     public void jumpDrawablesToCurrentState() {
5485         super.jumpDrawablesToCurrentState();
5486         if (mDrawables != null) {
5487             for (Drawable dr : mDrawables.mShowing) {
5488                 if (dr != null) {
5489                     dr.jumpToCurrentState();
5490                 }
5491             }
5492         }
5493     }
5494 
5495     @Override
invalidateDrawable(@onNull Drawable drawable)5496     public void invalidateDrawable(@NonNull Drawable drawable) {
5497         boolean handled = false;
5498 
5499         if (verifyDrawable(drawable)) {
5500             final Rect dirty = drawable.getBounds();
5501             int scrollX = mScrollX;
5502             int scrollY = mScrollY;
5503 
5504             // IMPORTANT: The coordinates below are based on the coordinates computed
5505             // for each compound drawable in onDraw(). Make sure to update each section
5506             // accordingly.
5507             final TextView.Drawables drawables = mDrawables;
5508             if (drawables != null) {
5509                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5510                     final int compoundPaddingTop = getCompoundPaddingTop();
5511                     final int compoundPaddingBottom = getCompoundPaddingBottom();
5512                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5513 
5514                     scrollX += mPaddingLeft;
5515                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5516                     handled = true;
5517                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5518                     final int compoundPaddingTop = getCompoundPaddingTop();
5519                     final int compoundPaddingBottom = getCompoundPaddingBottom();
5520                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5521 
5522                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5523                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5524                     handled = true;
5525                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5526                     final int compoundPaddingLeft = getCompoundPaddingLeft();
5527                     final int compoundPaddingRight = getCompoundPaddingRight();
5528                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5529 
5530                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5531                     scrollY += mPaddingTop;
5532                     handled = true;
5533                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5534                     final int compoundPaddingLeft = getCompoundPaddingLeft();
5535                     final int compoundPaddingRight = getCompoundPaddingRight();
5536                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5537 
5538                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5539                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5540                     handled = true;
5541                 }
5542             }
5543 
5544             if (handled) {
5545                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5546                         dirty.right + scrollX, dirty.bottom + scrollY);
5547             }
5548         }
5549 
5550         if (!handled) {
5551             super.invalidateDrawable(drawable);
5552         }
5553     }
5554 
5555     @Override
hasOverlappingRendering()5556     public boolean hasOverlappingRendering() {
5557         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5558         return ((getBackground() != null && getBackground().getCurrent() != null)
5559                 || mText instanceof Spannable || hasSelection()
5560                 || isHorizontalFadingEdgeEnabled());
5561     }
5562 
5563     /**
5564      *
5565      * Returns the state of the {@code textIsSelectable} flag (See
5566      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5567      * to allow users to select and copy text in a non-editable TextView, the content of an
5568      * {@link EditText} can always be selected, independently of the value of this flag.
5569      * <p>
5570      *
5571      * @return True if the text displayed in this TextView can be selected by the user.
5572      *
5573      * @attr ref android.R.styleable#TextView_textIsSelectable
5574      */
isTextSelectable()5575     public boolean isTextSelectable() {
5576         return mEditor == null ? false : mEditor.mTextIsSelectable;
5577     }
5578 
5579     /**
5580      * Sets whether the content of this view is selectable by the user. The default is
5581      * {@code false}, meaning that the content is not selectable.
5582      * <p>
5583      * When you use a TextView to display a useful piece of information to the user (such as a
5584      * contact's address), make it selectable, so that the user can select and copy its
5585      * content. You can also use set the XML attribute
5586      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5587      * <p>
5588      * When you call this method to set the value of {@code textIsSelectable}, it sets
5589      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5590      * and {@code longClickable} to the same value. These flags correspond to the attributes
5591      * {@link android.R.styleable#View_focusable android:focusable},
5592      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5593      * {@link android.R.styleable#View_clickable android:clickable}, and
5594      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5595      * flags to a state you had set previously, call one or more of the following methods:
5596      * {@link #setFocusable(boolean) setFocusable()},
5597      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5598      * {@link #setClickable(boolean) setClickable()} or
5599      * {@link #setLongClickable(boolean) setLongClickable()}.
5600      *
5601      * @param selectable Whether the content of this TextView should be selectable.
5602      */
setTextIsSelectable(boolean selectable)5603     public void setTextIsSelectable(boolean selectable) {
5604         if (!selectable && mEditor == null) return; // false is default value with no edit data
5605 
5606         createEditorIfNeeded();
5607         if (mEditor.mTextIsSelectable == selectable) return;
5608 
5609         mEditor.mTextIsSelectable = selectable;
5610         setFocusableInTouchMode(selectable);
5611         setFocusable(selectable);
5612         setClickable(selectable);
5613         setLongClickable(selectable);
5614 
5615         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5616 
5617         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5618         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5619 
5620         // Called by setText above, but safer in case of future code changes
5621         mEditor.prepareCursorControllers();
5622     }
5623 
5624     @Override
onCreateDrawableState(int extraSpace)5625     protected int[] onCreateDrawableState(int extraSpace) {
5626         final int[] drawableState;
5627 
5628         if (mSingleLine) {
5629             drawableState = super.onCreateDrawableState(extraSpace);
5630         } else {
5631             drawableState = super.onCreateDrawableState(extraSpace + 1);
5632             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5633         }
5634 
5635         if (isTextSelectable()) {
5636             // Disable pressed state, which was introduced when TextView was made clickable.
5637             // Prevents text color change.
5638             // setClickable(false) would have a similar effect, but it also disables focus changes
5639             // and long press actions, which are both needed by text selection.
5640             final int length = drawableState.length;
5641             for (int i = 0; i < length; i++) {
5642                 if (drawableState[i] == R.attr.state_pressed) {
5643                     final int[] nonPressedState = new int[length - 1];
5644                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5645                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5646                     return nonPressedState;
5647                 }
5648             }
5649         }
5650 
5651         return drawableState;
5652     }
5653 
getUpdatedHighlightPath()5654     private Path getUpdatedHighlightPath() {
5655         Path highlight = null;
5656         Paint highlightPaint = mHighlightPaint;
5657 
5658         final int selStart = getSelectionStart();
5659         final int selEnd = getSelectionEnd();
5660         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5661             if (selStart == selEnd) {
5662                 if (mEditor != null && mEditor.isCursorVisible() &&
5663                         (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5664                         (2 * Editor.BLINK) < Editor.BLINK) {
5665                     if (mHighlightPathBogus) {
5666                         if (mHighlightPath == null) mHighlightPath = new Path();
5667                         mHighlightPath.reset();
5668                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
5669                         mEditor.updateCursorsPositions();
5670                         mHighlightPathBogus = false;
5671                     }
5672 
5673                     // XXX should pass to skin instead of drawing directly
5674                     highlightPaint.setColor(mCurTextColor);
5675                     highlightPaint.setStyle(Paint.Style.STROKE);
5676                     highlight = mHighlightPath;
5677                 }
5678             } else {
5679                 if (mHighlightPathBogus) {
5680                     if (mHighlightPath == null) mHighlightPath = new Path();
5681                     mHighlightPath.reset();
5682                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5683                     mHighlightPathBogus = false;
5684                 }
5685 
5686                 // XXX should pass to skin instead of drawing directly
5687                 highlightPaint.setColor(mHighlightColor);
5688                 highlightPaint.setStyle(Paint.Style.FILL);
5689 
5690                 highlight = mHighlightPath;
5691             }
5692         }
5693         return highlight;
5694     }
5695 
5696     /**
5697      * @hide
5698      */
getHorizontalOffsetForDrawables()5699     public int getHorizontalOffsetForDrawables() {
5700         return 0;
5701     }
5702 
5703     @Override
onDraw(Canvas canvas)5704     protected void onDraw(Canvas canvas) {
5705         restartMarqueeIfNeeded();
5706 
5707         // Draw the background for this view
5708         super.onDraw(canvas);
5709 
5710         final int compoundPaddingLeft = getCompoundPaddingLeft();
5711         final int compoundPaddingTop = getCompoundPaddingTop();
5712         final int compoundPaddingRight = getCompoundPaddingRight();
5713         final int compoundPaddingBottom = getCompoundPaddingBottom();
5714         final int scrollX = mScrollX;
5715         final int scrollY = mScrollY;
5716         final int right = mRight;
5717         final int left = mLeft;
5718         final int bottom = mBottom;
5719         final int top = mTop;
5720         final boolean isLayoutRtl = isLayoutRtl();
5721         final int offset = getHorizontalOffsetForDrawables();
5722         final int leftOffset = isLayoutRtl ? 0 : offset;
5723         final int rightOffset = isLayoutRtl ? offset : 0 ;
5724 
5725         final Drawables dr = mDrawables;
5726         if (dr != null) {
5727             /*
5728              * Compound, not extended, because the icon is not clipped
5729              * if the text height is smaller.
5730              */
5731 
5732             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5733             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5734 
5735             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5736             // Make sure to update invalidateDrawable() when changing this code.
5737             if (dr.mShowing[Drawables.LEFT] != null) {
5738                 canvas.save();
5739                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5740                                  scrollY + compoundPaddingTop +
5741                                  (vspace - dr.mDrawableHeightLeft) / 2);
5742                 dr.mShowing[Drawables.LEFT].draw(canvas);
5743                 canvas.restore();
5744             }
5745 
5746             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5747             // Make sure to update invalidateDrawable() when changing this code.
5748             if (dr.mShowing[Drawables.RIGHT] != null) {
5749                 canvas.save();
5750                 canvas.translate(scrollX + right - left - mPaddingRight
5751                         - dr.mDrawableSizeRight - rightOffset,
5752                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5753                 dr.mShowing[Drawables.RIGHT].draw(canvas);
5754                 canvas.restore();
5755             }
5756 
5757             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5758             // Make sure to update invalidateDrawable() when changing this code.
5759             if (dr.mShowing[Drawables.TOP] != null) {
5760                 canvas.save();
5761                 canvas.translate(scrollX + compoundPaddingLeft +
5762                         (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5763                 dr.mShowing[Drawables.TOP].draw(canvas);
5764                 canvas.restore();
5765             }
5766 
5767             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5768             // Make sure to update invalidateDrawable() when changing this code.
5769             if (dr.mShowing[Drawables.BOTTOM] != null) {
5770                 canvas.save();
5771                 canvas.translate(scrollX + compoundPaddingLeft +
5772                         (hspace - dr.mDrawableWidthBottom) / 2,
5773                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5774                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5775                 canvas.restore();
5776             }
5777         }
5778 
5779         int color = mCurTextColor;
5780 
5781         if (mLayout == null) {
5782             assumeLayout();
5783         }
5784 
5785         Layout layout = mLayout;
5786 
5787         if (mHint != null && mText.length() == 0) {
5788             if (mHintTextColor != null) {
5789                 color = mCurHintTextColor;
5790             }
5791 
5792             layout = mHintLayout;
5793         }
5794 
5795         mTextPaint.setColor(color);
5796         mTextPaint.drawableState = getDrawableState();
5797 
5798         canvas.save();
5799         /*  Would be faster if we didn't have to do this. Can we chop the
5800             (displayable) text so that we don't need to do this ever?
5801         */
5802 
5803         int extendedPaddingTop = getExtendedPaddingTop();
5804         int extendedPaddingBottom = getExtendedPaddingBottom();
5805 
5806         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5807         final int maxScrollY = mLayout.getHeight() - vspace;
5808 
5809         float clipLeft = compoundPaddingLeft + scrollX;
5810         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5811         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
5812         float clipBottom = bottom - top + scrollY -
5813                 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5814 
5815         if (mShadowRadius != 0) {
5816             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5817             clipRight += Math.max(0, mShadowDx + mShadowRadius);
5818 
5819             clipTop += Math.min(0, mShadowDy - mShadowRadius);
5820             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5821         }
5822 
5823         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5824 
5825         int voffsetText = 0;
5826         int voffsetCursor = 0;
5827 
5828         // translate in by our padding
5829         /* shortcircuit calling getVerticaOffset() */
5830         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5831             voffsetText = getVerticalOffset(false);
5832             voffsetCursor = getVerticalOffset(true);
5833         }
5834         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5835 
5836         final int layoutDirection = getLayoutDirection();
5837         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5838         if (isMarqueeFadeEnabled()) {
5839             if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5840                     (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5841                 final int width = mRight - mLeft;
5842                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5843                 final float dx = mLayout.getLineRight(0) - (width - padding);
5844                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5845             }
5846 
5847             if (mMarquee != null && mMarquee.isRunning()) {
5848                 final float dx = -mMarquee.getScroll();
5849                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5850             }
5851         }
5852 
5853         final int cursorOffsetVertical = voffsetCursor - voffsetText;
5854 
5855         Path highlight = getUpdatedHighlightPath();
5856         if (mEditor != null) {
5857             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5858         } else {
5859             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5860         }
5861 
5862         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5863             final float dx = mMarquee.getGhostOffset();
5864             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5865             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5866         }
5867 
5868         canvas.restore();
5869     }
5870 
5871     @Override
getFocusedRect(Rect r)5872     public void getFocusedRect(Rect r) {
5873         if (mLayout == null) {
5874             super.getFocusedRect(r);
5875             return;
5876         }
5877 
5878         int selEnd = getSelectionEnd();
5879         if (selEnd < 0) {
5880             super.getFocusedRect(r);
5881             return;
5882         }
5883 
5884         int selStart = getSelectionStart();
5885         if (selStart < 0 || selStart >= selEnd) {
5886             int line = mLayout.getLineForOffset(selEnd);
5887             r.top = mLayout.getLineTop(line);
5888             r.bottom = mLayout.getLineBottom(line);
5889             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5890             r.right = r.left + 4;
5891         } else {
5892             int lineStart = mLayout.getLineForOffset(selStart);
5893             int lineEnd = mLayout.getLineForOffset(selEnd);
5894             r.top = mLayout.getLineTop(lineStart);
5895             r.bottom = mLayout.getLineBottom(lineEnd);
5896             if (lineStart == lineEnd) {
5897                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5898                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5899             } else {
5900                 // Selection extends across multiple lines -- make the focused
5901                 // rect cover the entire width.
5902                 if (mHighlightPathBogus) {
5903                     if (mHighlightPath == null) mHighlightPath = new Path();
5904                     mHighlightPath.reset();
5905                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5906                     mHighlightPathBogus = false;
5907                 }
5908                 synchronized (TEMP_RECTF) {
5909                     mHighlightPath.computeBounds(TEMP_RECTF, true);
5910                     r.left = (int)TEMP_RECTF.left-1;
5911                     r.right = (int)TEMP_RECTF.right+1;
5912                 }
5913             }
5914         }
5915 
5916         // Adjust for padding and gravity.
5917         int paddingLeft = getCompoundPaddingLeft();
5918         int paddingTop = getExtendedPaddingTop();
5919         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5920             paddingTop += getVerticalOffset(false);
5921         }
5922         r.offset(paddingLeft, paddingTop);
5923         int paddingBottom = getExtendedPaddingBottom();
5924         r.bottom += paddingBottom;
5925     }
5926 
5927     /**
5928      * Return the number of lines of text, or 0 if the internal Layout has not
5929      * been built.
5930      */
getLineCount()5931     public int getLineCount() {
5932         return mLayout != null ? mLayout.getLineCount() : 0;
5933     }
5934 
5935     /**
5936      * Return the baseline for the specified line (0...getLineCount() - 1)
5937      * If bounds is not null, return the top, left, right, bottom extents
5938      * of the specified line in it. If the internal Layout has not been built,
5939      * return 0 and set bounds to (0, 0, 0, 0)
5940      * @param line which line to examine (0..getLineCount() - 1)
5941      * @param bounds Optional. If not null, it returns the extent of the line
5942      * @return the Y-coordinate of the baseline
5943      */
getLineBounds(int line, Rect bounds)5944     public int getLineBounds(int line, Rect bounds) {
5945         if (mLayout == null) {
5946             if (bounds != null) {
5947                 bounds.set(0, 0, 0, 0);
5948             }
5949             return 0;
5950         }
5951         else {
5952             int baseline = mLayout.getLineBounds(line, bounds);
5953 
5954             int voffset = getExtendedPaddingTop();
5955             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5956                 voffset += getVerticalOffset(true);
5957             }
5958             if (bounds != null) {
5959                 bounds.offset(getCompoundPaddingLeft(), voffset);
5960             }
5961             return baseline + voffset;
5962         }
5963     }
5964 
5965     @Override
getBaseline()5966     public int getBaseline() {
5967         if (mLayout == null) {
5968             return super.getBaseline();
5969         }
5970 
5971         return getBaselineOffset() + mLayout.getLineBaseline(0);
5972     }
5973 
getBaselineOffset()5974     int getBaselineOffset() {
5975         int voffset = 0;
5976         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5977             voffset = getVerticalOffset(true);
5978         }
5979 
5980         if (isLayoutModeOptical(mParent)) {
5981             voffset -= getOpticalInsets().top;
5982         }
5983 
5984         return getExtendedPaddingTop() + voffset;
5985     }
5986 
5987     /**
5988      * @hide
5989      */
5990     @Override
getFadeTop(boolean offsetRequired)5991     protected int getFadeTop(boolean offsetRequired) {
5992         if (mLayout == null) return 0;
5993 
5994         int voffset = 0;
5995         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5996             voffset = getVerticalOffset(true);
5997         }
5998 
5999         if (offsetRequired) voffset += getTopPaddingOffset();
6000 
6001         return getExtendedPaddingTop() + voffset;
6002     }
6003 
6004     /**
6005      * @hide
6006      */
6007     @Override
getFadeHeight(boolean offsetRequired)6008     protected int getFadeHeight(boolean offsetRequired) {
6009         return mLayout != null ? mLayout.getHeight() : 0;
6010     }
6011 
6012     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)6013     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
6014         if (mText instanceof Spannable && mLinksClickable) {
6015             final float x = event.getX(pointerIndex);
6016             final float y = event.getY(pointerIndex);
6017             final int offset = getOffsetForPosition(x, y);
6018             final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
6019                     ClickableSpan.class);
6020             if (clickables.length > 0) {
6021                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
6022             }
6023         }
6024         if (isTextSelectable() || isTextEditable()) {
6025             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
6026         }
6027         return super.onResolvePointerIcon(event, pointerIndex);
6028     }
6029 
6030     @Override
onKeyPreIme(int keyCode, KeyEvent event)6031     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
6032         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
6033         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
6034         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
6035         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
6036             return true;
6037         }
6038         return super.onKeyPreIme(keyCode, event);
6039     }
6040 
6041     /**
6042      * @hide
6043      */
handleBackInTextActionModeIfNeeded(KeyEvent event)6044     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
6045         // Do nothing unless mEditor is in text action mode.
6046         if (mEditor == null || mEditor.mTextActionMode == null) {
6047             return false;
6048         }
6049 
6050         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
6051             KeyEvent.DispatcherState state = getKeyDispatcherState();
6052             if (state != null) {
6053                 state.startTracking(event, this);
6054             }
6055             return true;
6056         } else if (event.getAction() == KeyEvent.ACTION_UP) {
6057             KeyEvent.DispatcherState state = getKeyDispatcherState();
6058             if (state != null) {
6059                 state.handleUpEvent(event);
6060             }
6061             if (event.isTracking() && !event.isCanceled()) {
6062                 stopTextActionMode();
6063                 return true;
6064             }
6065         }
6066         return false;
6067     }
6068 
6069     @Override
onKeyDown(int keyCode, KeyEvent event)6070     public boolean onKeyDown(int keyCode, KeyEvent event) {
6071         final int which = doKeyDown(keyCode, event, null);
6072         if (which == KEY_EVENT_NOT_HANDLED) {
6073             return super.onKeyDown(keyCode, event);
6074         }
6075 
6076         return true;
6077     }
6078 
6079     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)6080     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
6081         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
6082         final int which = doKeyDown(keyCode, down, event);
6083         if (which == KEY_EVENT_NOT_HANDLED) {
6084             // Go through default dispatching.
6085             return super.onKeyMultiple(keyCode, repeatCount, event);
6086         }
6087         if (which == KEY_EVENT_HANDLED) {
6088             // Consumed the whole thing.
6089             return true;
6090         }
6091 
6092         repeatCount--;
6093 
6094         // We are going to dispatch the remaining events to either the input
6095         // or movement method.  To do this, we will just send a repeated stream
6096         // of down and up events until we have done the complete repeatCount.
6097         // It would be nice if those interfaces had an onKeyMultiple() method,
6098         // but adding that is a more complicated change.
6099         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
6100         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
6101             // mEditor and mEditor.mInput are not null from doKeyDown
6102             mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6103             while (--repeatCount > 0) {
6104                 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
6105                 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6106             }
6107             hideErrorIfUnchanged();
6108 
6109         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
6110             // mMovement is not null from doKeyDown
6111             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6112             while (--repeatCount > 0) {
6113                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
6114                 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6115             }
6116         }
6117 
6118         return true;
6119     }
6120 
6121     /**
6122      * Returns true if pressing ENTER in this field advances focus instead
6123      * of inserting the character.  This is true mostly in single-line fields,
6124      * but also in mail addresses and subjects which will display on multiple
6125      * lines but where it doesn't make sense to insert newlines.
6126      */
shouldAdvanceFocusOnEnter()6127     private boolean shouldAdvanceFocusOnEnter() {
6128         if (getKeyListener() == null) {
6129             return false;
6130         }
6131 
6132         if (mSingleLine) {
6133             return true;
6134         }
6135 
6136         if (mEditor != null &&
6137                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6138             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6139             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6140                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
6141                 return true;
6142             }
6143         }
6144 
6145         return false;
6146     }
6147 
6148     /**
6149      * Returns true if pressing TAB in this field advances focus instead
6150      * of inserting the character.  Insert tabs only in multi-line editors.
6151      */
shouldAdvanceFocusOnTab()6152     private boolean shouldAdvanceFocusOnTab() {
6153         if (getKeyListener() != null && !mSingleLine && mEditor != null &&
6154                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6155             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6156             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6157                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6158                 return false;
6159             }
6160         }
6161         return true;
6162     }
6163 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)6164     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6165         if (!isEnabled()) {
6166             return KEY_EVENT_NOT_HANDLED;
6167         }
6168 
6169         // If this is the initial keydown, we don't want to prevent a movement away from this view.
6170         // While this shouldn't be necessary because any time we're preventing default movement we
6171         // should be restricting the focus to remain within this view, thus we'll also receive
6172         // the key up event, occasionally key up events will get dropped and we don't want to
6173         // prevent the user from traversing out of this on the next key down.
6174         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6175             mPreventDefaultMovement = false;
6176         }
6177 
6178         switch (keyCode) {
6179             case KeyEvent.KEYCODE_ENTER:
6180                 if (event.hasNoModifiers()) {
6181                     // When mInputContentType is set, we know that we are
6182                     // running in a "modern" cupcake environment, so don't need
6183                     // to worry about the application trying to capture
6184                     // enter key events.
6185                     if (mEditor != null && mEditor.mInputContentType != null) {
6186                         // If there is an action listener, given them a
6187                         // chance to consume the event.
6188                         if (mEditor.mInputContentType.onEditorActionListener != null &&
6189                                 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6190                                 this, EditorInfo.IME_NULL, event)) {
6191                             mEditor.mInputContentType.enterDown = true;
6192                             // We are consuming the enter key for them.
6193                             return KEY_EVENT_HANDLED;
6194                         }
6195                     }
6196 
6197                     // If our editor should move focus when enter is pressed, or
6198                     // this is a generated event from an IME action button, then
6199                     // don't let it be inserted into the text.
6200                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6201                             || shouldAdvanceFocusOnEnter()) {
6202                         if (hasOnClickListeners()) {
6203                             return KEY_EVENT_NOT_HANDLED;
6204                         }
6205                         return KEY_EVENT_HANDLED;
6206                     }
6207                 }
6208                 break;
6209 
6210             case KeyEvent.KEYCODE_DPAD_CENTER:
6211                 if (event.hasNoModifiers()) {
6212                     if (shouldAdvanceFocusOnEnter()) {
6213                         return KEY_EVENT_NOT_HANDLED;
6214                     }
6215                 }
6216                 break;
6217 
6218             case KeyEvent.KEYCODE_TAB:
6219                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6220                     if (shouldAdvanceFocusOnTab()) {
6221                         return KEY_EVENT_NOT_HANDLED;
6222                     }
6223                 }
6224                 break;
6225 
6226                 // Has to be done on key down (and not on key up) to correctly be intercepted.
6227             case KeyEvent.KEYCODE_BACK:
6228                 if (mEditor != null && mEditor.mTextActionMode != null) {
6229                     stopTextActionMode();
6230                     return KEY_EVENT_HANDLED;
6231                 }
6232                 break;
6233 
6234             case KeyEvent.KEYCODE_CUT:
6235                 if (event.hasNoModifiers() && canCut()) {
6236                     if (onTextContextMenuItem(ID_CUT)) {
6237                         return KEY_EVENT_HANDLED;
6238                     }
6239                 }
6240                 break;
6241 
6242             case KeyEvent.KEYCODE_COPY:
6243                 if (event.hasNoModifiers() && canCopy()) {
6244                     if (onTextContextMenuItem(ID_COPY)) {
6245                         return KEY_EVENT_HANDLED;
6246                     }
6247                 }
6248                 break;
6249 
6250             case KeyEvent.KEYCODE_PASTE:
6251                 if (event.hasNoModifiers() && canPaste()) {
6252                     if (onTextContextMenuItem(ID_PASTE)) {
6253                         return KEY_EVENT_HANDLED;
6254                     }
6255                 }
6256                 break;
6257         }
6258 
6259         if (mEditor != null && mEditor.mKeyListener != null) {
6260             boolean doDown = true;
6261             if (otherEvent != null) {
6262                 try {
6263                     beginBatchEdit();
6264                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6265                             otherEvent);
6266                     hideErrorIfUnchanged();
6267                     doDown = false;
6268                     if (handled) {
6269                         return KEY_EVENT_HANDLED;
6270                     }
6271                 } catch (AbstractMethodError e) {
6272                     // onKeyOther was added after 1.0, so if it isn't
6273                     // implemented we need to try to dispatch as a regular down.
6274                 } finally {
6275                     endBatchEdit();
6276                 }
6277             }
6278 
6279             if (doDown) {
6280                 beginBatchEdit();
6281                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6282                         keyCode, event);
6283                 endBatchEdit();
6284                 hideErrorIfUnchanged();
6285                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
6286             }
6287         }
6288 
6289         // bug 650865: sometimes we get a key event before a layout.
6290         // don't try to move around if we don't know the layout.
6291 
6292         if (mMovement != null && mLayout != null) {
6293             boolean doDown = true;
6294             if (otherEvent != null) {
6295                 try {
6296                     boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6297                             otherEvent);
6298                     doDown = false;
6299                     if (handled) {
6300                         return KEY_EVENT_HANDLED;
6301                     }
6302                 } catch (AbstractMethodError e) {
6303                     // onKeyOther was added after 1.0, so if it isn't
6304                     // implemented we need to try to dispatch as a regular down.
6305                 }
6306             }
6307             if (doDown) {
6308                 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6309                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6310                         mPreventDefaultMovement = true;
6311                     }
6312                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
6313                 }
6314             }
6315         }
6316 
6317         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
6318                 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
6319     }
6320 
6321     /**
6322      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6323      * can be recorded.
6324      * @hide
6325      */
resetErrorChangedFlag()6326     public void resetErrorChangedFlag() {
6327         /*
6328          * Keep track of what the error was before doing the input
6329          * so that if an input filter changed the error, we leave
6330          * that error showing.  Otherwise, we take down whatever
6331          * error was showing when the user types something.
6332          */
6333         if (mEditor != null) mEditor.mErrorWasChanged = false;
6334     }
6335 
6336     /**
6337      * @hide
6338      */
hideErrorIfUnchanged()6339     public void hideErrorIfUnchanged() {
6340         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6341             setError(null, null);
6342         }
6343     }
6344 
6345     @Override
onKeyUp(int keyCode, KeyEvent event)6346     public boolean onKeyUp(int keyCode, KeyEvent event) {
6347         if (!isEnabled()) {
6348             return super.onKeyUp(keyCode, event);
6349         }
6350 
6351         if (!KeyEvent.isModifierKey(keyCode)) {
6352             mPreventDefaultMovement = false;
6353         }
6354 
6355         switch (keyCode) {
6356             case KeyEvent.KEYCODE_DPAD_CENTER:
6357                 if (event.hasNoModifiers()) {
6358                     /*
6359                      * If there is a click listener, just call through to
6360                      * super, which will invoke it.
6361                      *
6362                      * If there isn't a click listener, try to show the soft
6363                      * input method.  (It will also
6364                      * call performClick(), but that won't do anything in
6365                      * this case.)
6366                      */
6367                     if (!hasOnClickListeners()) {
6368                         if (mMovement != null && mText instanceof Editable
6369                                 && mLayout != null && onCheckIsTextEditor()) {
6370                             InputMethodManager imm = InputMethodManager.peekInstance();
6371                             viewClicked(imm);
6372                             if (imm != null && getShowSoftInputOnFocus()) {
6373                                 imm.showSoftInput(this, 0);
6374                             }
6375                         }
6376                     }
6377                 }
6378                 return super.onKeyUp(keyCode, event);
6379 
6380             case KeyEvent.KEYCODE_ENTER:
6381                 if (event.hasNoModifiers()) {
6382                     if (mEditor != null && mEditor.mInputContentType != null
6383                             && mEditor.mInputContentType.onEditorActionListener != null
6384                             && mEditor.mInputContentType.enterDown) {
6385                         mEditor.mInputContentType.enterDown = false;
6386                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6387                                 this, EditorInfo.IME_NULL, event)) {
6388                             return true;
6389                         }
6390                     }
6391 
6392                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6393                             || shouldAdvanceFocusOnEnter()) {
6394                         /*
6395                          * If there is a click listener, just call through to
6396                          * super, which will invoke it.
6397                          *
6398                          * If there isn't a click listener, try to advance focus,
6399                          * but still call through to super, which will reset the
6400                          * pressed state and longpress state.  (It will also
6401                          * call performClick(), but that won't do anything in
6402                          * this case.)
6403                          */
6404                         if (!hasOnClickListeners()) {
6405                             View v = focusSearch(FOCUS_DOWN);
6406 
6407                             if (v != null) {
6408                                 if (!v.requestFocus(FOCUS_DOWN)) {
6409                                     throw new IllegalStateException(
6410                                             "focus search returned a view " +
6411                                             "that wasn't able to take focus!");
6412                                 }
6413 
6414                                 /*
6415                                  * Return true because we handled the key; super
6416                                  * will return false because there was no click
6417                                  * listener.
6418                                  */
6419                                 super.onKeyUp(keyCode, event);
6420                                 return true;
6421                             } else if ((event.getFlags()
6422                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6423                                 // No target for next focus, but make sure the IME
6424                                 // if this came from it.
6425                                 InputMethodManager imm = InputMethodManager.peekInstance();
6426                                 if (imm != null && imm.isActive(this)) {
6427                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
6428                                 }
6429                             }
6430                         }
6431                     }
6432                     return super.onKeyUp(keyCode, event);
6433                 }
6434                 break;
6435         }
6436 
6437         if (mEditor != null && mEditor.mKeyListener != null)
6438             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6439                 return true;
6440 
6441         if (mMovement != null && mLayout != null)
6442             if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6443                 return true;
6444 
6445         return super.onKeyUp(keyCode, event);
6446     }
6447 
6448     @Override
onCheckIsTextEditor()6449     public boolean onCheckIsTextEditor() {
6450         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6451     }
6452 
6453     @Override
onCreateInputConnection(EditorInfo outAttrs)6454     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6455         if (onCheckIsTextEditor() && isEnabled()) {
6456             mEditor.createInputMethodStateIfNeeded();
6457             outAttrs.inputType = getInputType();
6458             if (mEditor.mInputContentType != null) {
6459                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6460                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6461                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6462                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6463                 outAttrs.extras = mEditor.mInputContentType.extras;
6464                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
6465             } else {
6466                 outAttrs.imeOptions = EditorInfo.IME_NULL;
6467                 outAttrs.hintLocales = null;
6468             }
6469             if (focusSearch(FOCUS_DOWN) != null) {
6470                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6471             }
6472             if (focusSearch(FOCUS_UP) != null) {
6473                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6474             }
6475             if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6476                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
6477                 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6478                     // An action has not been set, but the enter key will move to
6479                     // the next focus, so set the action to that.
6480                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6481                 } else {
6482                     // An action has not been set, and there is no focus to move
6483                     // to, so let's just supply a "done" action.
6484                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6485                 }
6486                 if (!shouldAdvanceFocusOnEnter()) {
6487                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6488                 }
6489             }
6490             if (isMultilineInputType(outAttrs.inputType)) {
6491                 // Multi-line text editors should always show an enter key.
6492                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6493             }
6494             outAttrs.hintText = mHint;
6495             if (mText instanceof Editable) {
6496                 InputConnection ic = new EditableInputConnection(this);
6497                 outAttrs.initialSelStart = getSelectionStart();
6498                 outAttrs.initialSelEnd = getSelectionEnd();
6499                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6500                 return ic;
6501             }
6502         }
6503         return null;
6504     }
6505 
6506     /**
6507      * If this TextView contains editable content, extract a portion of it
6508      * based on the information in <var>request</var> in to <var>outText</var>.
6509      * @return Returns true if the text was successfully extracted, else false.
6510      */
extractText(ExtractedTextRequest request, ExtractedText outText)6511     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6512         createEditorIfNeeded();
6513         return mEditor.extractText(request, outText);
6514     }
6515 
6516     /**
6517      * This is used to remove all style-impacting spans from text before new
6518      * extracted text is being replaced into it, so that we don't have any
6519      * lingering spans applied during the replace.
6520      */
removeParcelableSpans(Spannable spannable, int start, int end)6521     static void removeParcelableSpans(Spannable spannable, int start, int end) {
6522         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6523         int i = spans.length;
6524         while (i > 0) {
6525             i--;
6526             spannable.removeSpan(spans[i]);
6527         }
6528     }
6529 
6530     /**
6531      * Apply to this text view the given extracted text, as previously
6532      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6533      */
setExtractedText(ExtractedText text)6534     public void setExtractedText(ExtractedText text) {
6535         Editable content = getEditableText();
6536         if (text.text != null) {
6537             if (content == null) {
6538                 setText(text.text, TextView.BufferType.EDITABLE);
6539             } else {
6540                 int start = 0;
6541                 int end = content.length();
6542 
6543                 if (text.partialStartOffset >= 0) {
6544                     final int N = content.length();
6545                     start = text.partialStartOffset;
6546                     if (start > N) start = N;
6547                     end = text.partialEndOffset;
6548                     if (end > N) end = N;
6549                 }
6550 
6551                 removeParcelableSpans(content, start, end);
6552                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6553                     if (text.text instanceof Spanned) {
6554                         // OK to copy spans only.
6555                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
6556                                 Object.class, content, start);
6557                     }
6558                 } else {
6559                     content.replace(start, end, text.text);
6560                 }
6561             }
6562         }
6563 
6564         // Now set the selection position...  make sure it is in range, to
6565         // avoid crashes.  If this is a partial update, it is possible that
6566         // the underlying text may have changed, causing us problems here.
6567         // Also we just don't want to trust clients to do the right thing.
6568         Spannable sp = (Spannable)getText();
6569         final int N = sp.length();
6570         int start = text.selectionStart;
6571         if (start < 0) start = 0;
6572         else if (start > N) start = N;
6573         int end = text.selectionEnd;
6574         if (end < 0) end = 0;
6575         else if (end > N) end = N;
6576         Selection.setSelection(sp, start, end);
6577 
6578         // Finally, update the selection mode.
6579         if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6580             MetaKeyKeyListener.startSelecting(this, sp);
6581         } else {
6582             MetaKeyKeyListener.stopSelecting(this, sp);
6583         }
6584     }
6585 
6586     /**
6587      * @hide
6588      */
setExtracting(ExtractedTextRequest req)6589     public void setExtracting(ExtractedTextRequest req) {
6590         if (mEditor.mInputMethodState != null) {
6591             mEditor.mInputMethodState.mExtractedTextRequest = req;
6592         }
6593         // This would stop a possible selection mode, but no such mode is started in case
6594         // extracted mode will start. Some text is selected though, and will trigger an action mode
6595         // in the extracted view.
6596         mEditor.hideCursorAndSpanControllers();
6597         stopTextActionMode();
6598         if (mEditor.mSelectionModifierCursorController != null) {
6599             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
6600         }
6601     }
6602 
6603     /**
6604      * Called by the framework in response to a text completion from
6605      * the current input method, provided by it calling
6606      * {@link InputConnection#commitCompletion
6607      * InputConnection.commitCompletion()}.  The default implementation does
6608      * nothing; text views that are supporting auto-completion should override
6609      * this to do their desired behavior.
6610      *
6611      * @param text The auto complete text the user has selected.
6612      */
onCommitCompletion(CompletionInfo text)6613     public void onCommitCompletion(CompletionInfo text) {
6614         // intentionally empty
6615     }
6616 
6617     /**
6618      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6619      * a dictionnary) from the current input method, provided by it calling
6620      * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6621      * implementation flashes the background of the corrected word to provide feedback to the user.
6622      *
6623      * @param info The auto correct info about the text that was corrected.
6624      */
onCommitCorrection(CorrectionInfo info)6625     public void onCommitCorrection(CorrectionInfo info) {
6626         if (mEditor != null) mEditor.onCommitCorrection(info);
6627     }
6628 
beginBatchEdit()6629     public void beginBatchEdit() {
6630         if (mEditor != null) mEditor.beginBatchEdit();
6631     }
6632 
endBatchEdit()6633     public void endBatchEdit() {
6634         if (mEditor != null) mEditor.endBatchEdit();
6635     }
6636 
6637     /**
6638      * Called by the framework in response to a request to begin a batch
6639      * of edit operations through a call to link {@link #beginBatchEdit()}.
6640      */
onBeginBatchEdit()6641     public void onBeginBatchEdit() {
6642         // intentionally empty
6643     }
6644 
6645     /**
6646      * Called by the framework in response to a request to end a batch
6647      * of edit operations through a call to link {@link #endBatchEdit}.
6648      */
onEndBatchEdit()6649     public void onEndBatchEdit() {
6650         // intentionally empty
6651     }
6652 
6653     /**
6654      * Called by the framework in response to a private command from the
6655      * current method, provided by it calling
6656      * {@link InputConnection#performPrivateCommand
6657      * InputConnection.performPrivateCommand()}.
6658      *
6659      * @param action The action name of the command.
6660      * @param data Any additional data for the command.  This may be null.
6661      * @return Return true if you handled the command, else false.
6662      */
onPrivateIMECommand(String action, Bundle data)6663     public boolean onPrivateIMECommand(String action, Bundle data) {
6664         return false;
6665     }
6666 
nullLayouts()6667     private void nullLayouts() {
6668         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6669             mSavedLayout = (BoringLayout) mLayout;
6670         }
6671         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6672             mSavedHintLayout = (BoringLayout) mHintLayout;
6673         }
6674 
6675         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6676 
6677         mBoring = mHintBoring = null;
6678 
6679         // Since it depends on the value of mLayout
6680         if (mEditor != null) mEditor.prepareCursorControllers();
6681     }
6682 
6683     /**
6684      * Make a new Layout based on the already-measured size of the view,
6685      * on the assumption that it was measured correctly at some point.
6686      */
assumeLayout()6687     private void assumeLayout() {
6688         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6689 
6690         if (width < 1) {
6691             width = 0;
6692         }
6693 
6694         int physicalWidth = width;
6695 
6696         if (mHorizontallyScrolling) {
6697             width = VERY_WIDE;
6698         }
6699 
6700         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6701                       physicalWidth, false);
6702     }
6703 
getLayoutAlignment()6704     private Layout.Alignment getLayoutAlignment() {
6705         Layout.Alignment alignment;
6706         switch (getTextAlignment()) {
6707             case TEXT_ALIGNMENT_GRAVITY:
6708                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6709                     case Gravity.START:
6710                         alignment = Layout.Alignment.ALIGN_NORMAL;
6711                         break;
6712                     case Gravity.END:
6713                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
6714                         break;
6715                     case Gravity.LEFT:
6716                         alignment = Layout.Alignment.ALIGN_LEFT;
6717                         break;
6718                     case Gravity.RIGHT:
6719                         alignment = Layout.Alignment.ALIGN_RIGHT;
6720                         break;
6721                     case Gravity.CENTER_HORIZONTAL:
6722                         alignment = Layout.Alignment.ALIGN_CENTER;
6723                         break;
6724                     default:
6725                         alignment = Layout.Alignment.ALIGN_NORMAL;
6726                         break;
6727                 }
6728                 break;
6729             case TEXT_ALIGNMENT_TEXT_START:
6730                 alignment = Layout.Alignment.ALIGN_NORMAL;
6731                 break;
6732             case TEXT_ALIGNMENT_TEXT_END:
6733                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6734                 break;
6735             case TEXT_ALIGNMENT_CENTER:
6736                 alignment = Layout.Alignment.ALIGN_CENTER;
6737                 break;
6738             case TEXT_ALIGNMENT_VIEW_START:
6739                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6740                         Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6741                 break;
6742             case TEXT_ALIGNMENT_VIEW_END:
6743                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6744                         Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6745                 break;
6746             case TEXT_ALIGNMENT_INHERIT:
6747                 // This should never happen as we have already resolved the text alignment
6748                 // but better safe than sorry so we just fall through
6749             default:
6750                 alignment = Layout.Alignment.ALIGN_NORMAL;
6751                 break;
6752         }
6753         return alignment;
6754     }
6755 
6756     /**
6757      * The width passed in is now the desired layout width,
6758      * not the full view width with padding.
6759      * {@hide}
6760      */
makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)6761     protected void makeNewLayout(int wantWidth, int hintWidth,
6762                                  BoringLayout.Metrics boring,
6763                                  BoringLayout.Metrics hintBoring,
6764                                  int ellipsisWidth, boolean bringIntoView) {
6765         stopMarquee();
6766 
6767         // Update "old" cached values
6768         mOldMaximum = mMaximum;
6769         mOldMaxMode = mMaxMode;
6770 
6771         mHighlightPathBogus = true;
6772 
6773         if (wantWidth < 0) {
6774             wantWidth = 0;
6775         }
6776         if (hintWidth < 0) {
6777             hintWidth = 0;
6778         }
6779 
6780         Layout.Alignment alignment = getLayoutAlignment();
6781         final boolean testDirChange = mSingleLine && mLayout != null &&
6782             (alignment == Layout.Alignment.ALIGN_NORMAL ||
6783              alignment == Layout.Alignment.ALIGN_OPPOSITE);
6784         int oldDir = 0;
6785         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6786         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6787         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6788                 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6789         TruncateAt effectiveEllipsize = mEllipsize;
6790         if (mEllipsize == TruncateAt.MARQUEE &&
6791                 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6792             effectiveEllipsize = TruncateAt.END_SMALL;
6793         }
6794 
6795         if (mTextDir == null) {
6796             mTextDir = getTextDirectionHeuristic();
6797         }
6798 
6799         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6800                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6801         if (switchEllipsize) {
6802             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6803                     TruncateAt.END : TruncateAt.MARQUEE;
6804             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6805                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6806         }
6807 
6808         shouldEllipsize = mEllipsize != null;
6809         mHintLayout = null;
6810 
6811         if (mHint != null) {
6812             if (shouldEllipsize) hintWidth = wantWidth;
6813 
6814             if (hintBoring == UNKNOWN_BORING) {
6815                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6816                                                    mHintBoring);
6817                 if (hintBoring != null) {
6818                     mHintBoring = hintBoring;
6819                 }
6820             }
6821 
6822             if (hintBoring != null) {
6823                 if (hintBoring.width <= hintWidth &&
6824                     (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6825                     if (mSavedHintLayout != null) {
6826                         mHintLayout = mSavedHintLayout.
6827                                 replaceOrMake(mHint, mTextPaint,
6828                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6829                                 hintBoring, mIncludePad);
6830                     } else {
6831                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
6832                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6833                                 hintBoring, mIncludePad);
6834                     }
6835 
6836                     mSavedHintLayout = (BoringLayout) mHintLayout;
6837                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6838                     if (mSavedHintLayout != null) {
6839                         mHintLayout = mSavedHintLayout.
6840                                 replaceOrMake(mHint, mTextPaint,
6841                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6842                                 hintBoring, mIncludePad, mEllipsize,
6843                                 ellipsisWidth);
6844                     } else {
6845                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
6846                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6847                                 hintBoring, mIncludePad, mEllipsize,
6848                                 ellipsisWidth);
6849                     }
6850                 }
6851             }
6852             // TODO: code duplication with makeSingleLayout()
6853             if (mHintLayout == null) {
6854                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6855                         mHint.length(), mTextPaint, hintWidth)
6856                         .setAlignment(alignment)
6857                         .setTextDirection(mTextDir)
6858                         .setLineSpacing(mSpacingAdd, mSpacingMult)
6859                         .setIncludePad(mIncludePad)
6860                         .setBreakStrategy(mBreakStrategy)
6861                         .setHyphenationFrequency(mHyphenationFrequency);
6862                 if (shouldEllipsize) {
6863                     builder.setEllipsize(mEllipsize)
6864                             .setEllipsizedWidth(ellipsisWidth)
6865                             .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6866                 }
6867                 mHintLayout = builder.build();
6868             }
6869         }
6870 
6871         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6872             registerForPreDraw();
6873         }
6874 
6875         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6876             if (!compressText(ellipsisWidth)) {
6877                 final int height = mLayoutParams.height;
6878                 // If the size of the view does not depend on the size of the text, try to
6879                 // start the marquee immediately
6880                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6881                     startMarquee();
6882                 } else {
6883                     // Defer the start of the marquee until we know our width (see setFrame())
6884                     mRestartMarquee = true;
6885                 }
6886             }
6887         }
6888 
6889         // CursorControllers need a non-null mLayout
6890         if (mEditor != null) mEditor.prepareCursorControllers();
6891     }
6892 
6893     /**
6894      * @hide
6895      */
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)6896     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6897             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6898             boolean useSaved) {
6899         Layout result = null;
6900         if (mText instanceof Spannable) {
6901             result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6902                     alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6903                     mBreakStrategy, mHyphenationFrequency,
6904                     getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6905         } else {
6906             if (boring == UNKNOWN_BORING) {
6907                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6908                 if (boring != null) {
6909                     mBoring = boring;
6910                 }
6911             }
6912 
6913             if (boring != null) {
6914                 if (boring.width <= wantWidth &&
6915                         (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6916                     if (useSaved && mSavedLayout != null) {
6917                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6918                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6919                                 boring, mIncludePad);
6920                     } else {
6921                         result = BoringLayout.make(mTransformed, mTextPaint,
6922                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6923                                 boring, mIncludePad);
6924                     }
6925 
6926                     if (useSaved) {
6927                         mSavedLayout = (BoringLayout) result;
6928                     }
6929                 } else if (shouldEllipsize && boring.width <= wantWidth) {
6930                     if (useSaved && mSavedLayout != null) {
6931                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6932                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6933                                 boring, mIncludePad, effectiveEllipsize,
6934                                 ellipsisWidth);
6935                     } else {
6936                         result = BoringLayout.make(mTransformed, mTextPaint,
6937                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6938                                 boring, mIncludePad, effectiveEllipsize,
6939                                 ellipsisWidth);
6940                     }
6941                 }
6942             }
6943         }
6944         if (result == null) {
6945             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6946                     0, mTransformed.length(), mTextPaint, wantWidth)
6947                     .setAlignment(alignment)
6948                     .setTextDirection(mTextDir)
6949                     .setLineSpacing(mSpacingAdd, mSpacingMult)
6950                     .setIncludePad(mIncludePad)
6951                     .setBreakStrategy(mBreakStrategy)
6952                     .setHyphenationFrequency(mHyphenationFrequency);
6953             if (shouldEllipsize) {
6954                 builder.setEllipsize(effectiveEllipsize)
6955                         .setEllipsizedWidth(ellipsisWidth)
6956                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6957             }
6958             // TODO: explore always setting maxLines
6959             result = builder.build();
6960         }
6961         return result;
6962     }
6963 
compressText(float width)6964     private boolean compressText(float width) {
6965         if (isHardwareAccelerated()) return false;
6966 
6967         // Only compress the text if it hasn't been compressed by the previous pass
6968         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6969                 mTextPaint.getTextScaleX() == 1.0f) {
6970             final float textWidth = mLayout.getLineWidth(0);
6971             final float overflow = (textWidth + 1.0f - width) / width;
6972             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6973                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6974                 post(new Runnable() {
6975                     public void run() {
6976                         requestLayout();
6977                     }
6978                 });
6979                 return true;
6980             }
6981         }
6982 
6983         return false;
6984     }
6985 
desired(Layout layout)6986     private static int desired(Layout layout) {
6987         int n = layout.getLineCount();
6988         CharSequence text = layout.getText();
6989         float max = 0;
6990 
6991         // if any line was wrapped, we can't use it.
6992         // but it's ok for the last line not to have a newline
6993 
6994         for (int i = 0; i < n - 1; i++) {
6995             if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6996                 return -1;
6997         }
6998 
6999         for (int i = 0; i < n; i++) {
7000             max = Math.max(max, layout.getLineWidth(i));
7001         }
7002 
7003         return (int) Math.ceil(max);
7004     }
7005 
7006     /**
7007      * Set whether the TextView includes extra top and bottom padding to make
7008      * room for accents that go above the normal ascent and descent.
7009      * The default is true.
7010      *
7011      * @see #getIncludeFontPadding()
7012      *
7013      * @attr ref android.R.styleable#TextView_includeFontPadding
7014      */
setIncludeFontPadding(boolean includepad)7015     public void setIncludeFontPadding(boolean includepad) {
7016         if (mIncludePad != includepad) {
7017             mIncludePad = includepad;
7018 
7019             if (mLayout != null) {
7020                 nullLayouts();
7021                 requestLayout();
7022                 invalidate();
7023             }
7024         }
7025     }
7026 
7027     /**
7028      * Gets whether the TextView includes extra top and bottom padding to make
7029      * room for accents that go above the normal ascent and descent.
7030      *
7031      * @see #setIncludeFontPadding(boolean)
7032      *
7033      * @attr ref android.R.styleable#TextView_includeFontPadding
7034      */
getIncludeFontPadding()7035     public boolean getIncludeFontPadding() {
7036         return mIncludePad;
7037     }
7038 
7039     private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
7040 
7041     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)7042     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7043         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7044         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7045         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7046         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7047 
7048         int width;
7049         int height;
7050 
7051         BoringLayout.Metrics boring = UNKNOWN_BORING;
7052         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
7053 
7054         if (mTextDir == null) {
7055             mTextDir = getTextDirectionHeuristic();
7056         }
7057 
7058         int des = -1;
7059         boolean fromexisting = false;
7060 
7061         if (widthMode == MeasureSpec.EXACTLY) {
7062             // Parent has told us how big to be. So be it.
7063             width = widthSize;
7064         } else {
7065             if (mLayout != null && mEllipsize == null) {
7066                 des = desired(mLayout);
7067             }
7068 
7069             if (des < 0) {
7070                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7071                 if (boring != null) {
7072                     mBoring = boring;
7073                 }
7074             } else {
7075                 fromexisting = true;
7076             }
7077 
7078             if (boring == null || boring == UNKNOWN_BORING) {
7079                 if (des < 0) {
7080                     des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
7081                 }
7082                 width = des;
7083             } else {
7084                 width = boring.width;
7085             }
7086 
7087             final Drawables dr = mDrawables;
7088             if (dr != null) {
7089                 width = Math.max(width, dr.mDrawableWidthTop);
7090                 width = Math.max(width, dr.mDrawableWidthBottom);
7091             }
7092 
7093             if (mHint != null) {
7094                 int hintDes = -1;
7095                 int hintWidth;
7096 
7097                 if (mHintLayout != null && mEllipsize == null) {
7098                     hintDes = desired(mHintLayout);
7099                 }
7100 
7101                 if (hintDes < 0) {
7102                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
7103                     if (hintBoring != null) {
7104                         mHintBoring = hintBoring;
7105                     }
7106                 }
7107 
7108                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7109                     if (hintDes < 0) {
7110                         hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
7111                     }
7112                     hintWidth = hintDes;
7113                 } else {
7114                     hintWidth = hintBoring.width;
7115                 }
7116 
7117                 if (hintWidth > width) {
7118                     width = hintWidth;
7119                 }
7120             }
7121 
7122             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7123 
7124             if (mMaxWidthMode == EMS) {
7125                 width = Math.min(width, mMaxWidth * getLineHeight());
7126             } else {
7127                 width = Math.min(width, mMaxWidth);
7128             }
7129 
7130             if (mMinWidthMode == EMS) {
7131                 width = Math.max(width, mMinWidth * getLineHeight());
7132             } else {
7133                 width = Math.max(width, mMinWidth);
7134             }
7135 
7136             // Check against our minimum width
7137             width = Math.max(width, getSuggestedMinimumWidth());
7138 
7139             if (widthMode == MeasureSpec.AT_MOST) {
7140                 width = Math.min(widthSize, width);
7141             }
7142         }
7143 
7144         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7145         int unpaddedWidth = want;
7146 
7147         if (mHorizontallyScrolling) want = VERY_WIDE;
7148 
7149         int hintWant = want;
7150         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
7151 
7152         if (mLayout == null) {
7153             makeNewLayout(want, hintWant, boring, hintBoring,
7154                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7155         } else {
7156             final boolean layoutChanged = (mLayout.getWidth() != want) ||
7157                     (hintWidth != hintWant) ||
7158                     (mLayout.getEllipsizedWidth() !=
7159                             width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7160 
7161             final boolean widthChanged = (mHint == null) &&
7162                     (mEllipsize == null) &&
7163                     (want > mLayout.getWidth()) &&
7164                     (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
7165 
7166             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7167 
7168             if (layoutChanged || maximumChanged) {
7169                 if (!maximumChanged && widthChanged) {
7170                     mLayout.increaseWidthTo(want);
7171                 } else {
7172                     makeNewLayout(want, hintWant, boring, hintBoring,
7173                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7174                 }
7175             } else {
7176                 // Nothing has changed
7177             }
7178         }
7179 
7180         if (heightMode == MeasureSpec.EXACTLY) {
7181             // Parent has told us how big to be. So be it.
7182             height = heightSize;
7183             mDesiredHeightAtMeasure = -1;
7184         } else {
7185             int desired = getDesiredHeight();
7186 
7187             height = desired;
7188             mDesiredHeightAtMeasure = desired;
7189 
7190             if (heightMode == MeasureSpec.AT_MOST) {
7191                 height = Math.min(desired, heightSize);
7192             }
7193         }
7194 
7195         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
7196         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
7197             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
7198         }
7199 
7200         /*
7201          * We didn't let makeNewLayout() register to bring the cursor into view,
7202          * so do it here if there is any possibility that it is needed.
7203          */
7204         if (mMovement != null ||
7205             mLayout.getWidth() > unpaddedWidth ||
7206             mLayout.getHeight() > unpaddedHeight) {
7207             registerForPreDraw();
7208         } else {
7209             scrollTo(0, 0);
7210         }
7211 
7212         setMeasuredDimension(width, height);
7213     }
7214 
getDesiredHeight()7215     private int getDesiredHeight() {
7216         return Math.max(
7217                 getDesiredHeight(mLayout, true),
7218                 getDesiredHeight(mHintLayout, mEllipsize != null));
7219     }
7220 
getDesiredHeight(Layout layout, boolean cap)7221     private int getDesiredHeight(Layout layout, boolean cap) {
7222         if (layout == null) {
7223             return 0;
7224         }
7225 
7226         int linecount = layout.getLineCount();
7227         int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7228         int desired = layout.getLineTop(linecount);
7229 
7230         final Drawables dr = mDrawables;
7231         if (dr != null) {
7232             desired = Math.max(desired, dr.mDrawableHeightLeft);
7233             desired = Math.max(desired, dr.mDrawableHeightRight);
7234         }
7235 
7236         desired += pad;
7237 
7238         if (mMaxMode == LINES) {
7239             /*
7240              * Don't cap the hint to a certain number of lines.
7241              * (Do cap it, though, if we have a maximum pixel height.)
7242              */
7243             if (cap) {
7244                 if (linecount > mMaximum) {
7245                     desired = layout.getLineTop(mMaximum);
7246 
7247                     if (dr != null) {
7248                         desired = Math.max(desired, dr.mDrawableHeightLeft);
7249                         desired = Math.max(desired, dr.mDrawableHeightRight);
7250                     }
7251 
7252                     desired += pad;
7253                     linecount = mMaximum;
7254                 }
7255             }
7256         } else {
7257             desired = Math.min(desired, mMaximum);
7258         }
7259 
7260         if (mMinMode == LINES) {
7261             if (linecount < mMinimum) {
7262                 desired += getLineHeight() * (mMinimum - linecount);
7263             }
7264         } else {
7265             desired = Math.max(desired, mMinimum);
7266         }
7267 
7268         // Check against our minimum height
7269         desired = Math.max(desired, getSuggestedMinimumHeight());
7270 
7271         return desired;
7272     }
7273 
7274     /**
7275      * Check whether a change to the existing text layout requires a
7276      * new view layout.
7277      */
checkForResize()7278     private void checkForResize() {
7279         boolean sizeChanged = false;
7280 
7281         if (mLayout != null) {
7282             // Check if our width changed
7283             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7284                 sizeChanged = true;
7285                 invalidate();
7286             }
7287 
7288             // Check if our height changed
7289             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7290                 int desiredHeight = getDesiredHeight();
7291 
7292                 if (desiredHeight != this.getHeight()) {
7293                     sizeChanged = true;
7294                 }
7295             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7296                 if (mDesiredHeightAtMeasure >= 0) {
7297                     int desiredHeight = getDesiredHeight();
7298 
7299                     if (desiredHeight != mDesiredHeightAtMeasure) {
7300                         sizeChanged = true;
7301                     }
7302                 }
7303             }
7304         }
7305 
7306         if (sizeChanged) {
7307             requestLayout();
7308             // caller will have already invalidated
7309         }
7310     }
7311 
7312     /**
7313      * Check whether entirely new text requires a new view layout
7314      * or merely a new text layout.
7315      */
checkForRelayout()7316     private void checkForRelayout() {
7317         // If we have a fixed width, we can just swap in a new text layout
7318         // if the text height stays the same or if the view height is fixed.
7319 
7320         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7321                 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7322                 (mHint == null || mHintLayout != null) &&
7323                 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7324             // Static width, so try making a new text layout.
7325 
7326             int oldht = mLayout.getHeight();
7327             int want = mLayout.getWidth();
7328             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7329 
7330             /*
7331              * No need to bring the text into view, since the size is not
7332              * changing (unless we do the requestLayout(), in which case it
7333              * will happen at measure).
7334              */
7335             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7336                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7337                           false);
7338 
7339             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7340                 // In a fixed-height view, so use our new text layout.
7341                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7342                     mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7343                     invalidate();
7344                     return;
7345                 }
7346 
7347                 // Dynamic height, but height has stayed the same,
7348                 // so use our new text layout.
7349                 if (mLayout.getHeight() == oldht &&
7350                     (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7351                     invalidate();
7352                     return;
7353                 }
7354             }
7355 
7356             // We lose: the height has changed and we have a dynamic height.
7357             // Request a new view layout using our new text layout.
7358             requestLayout();
7359             invalidate();
7360         } else {
7361             // Dynamic width, so we have no choice but to request a new
7362             // view layout with a new text layout.
7363             nullLayouts();
7364             requestLayout();
7365             invalidate();
7366         }
7367     }
7368 
7369     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)7370     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7371         super.onLayout(changed, left, top, right, bottom);
7372         if (mDeferScroll >= 0) {
7373             int curs = mDeferScroll;
7374             mDeferScroll = -1;
7375             bringPointIntoView(Math.min(curs, mText.length()));
7376         }
7377     }
7378 
isShowingHint()7379     private boolean isShowingHint() {
7380         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7381     }
7382 
7383     /**
7384      * Returns true if anything changed.
7385      */
bringTextIntoView()7386     private boolean bringTextIntoView() {
7387         Layout layout = isShowingHint() ? mHintLayout : mLayout;
7388         int line = 0;
7389         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7390             line = layout.getLineCount() - 1;
7391         }
7392 
7393         Layout.Alignment a = layout.getParagraphAlignment(line);
7394         int dir = layout.getParagraphDirection(line);
7395         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7396         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7397         int ht = layout.getHeight();
7398 
7399         int scrollx, scrolly;
7400 
7401         // Convert to left, center, or right alignment.
7402         if (a == Layout.Alignment.ALIGN_NORMAL) {
7403             a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7404                 Layout.Alignment.ALIGN_RIGHT;
7405         } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7406             a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7407                 Layout.Alignment.ALIGN_LEFT;
7408         }
7409 
7410         if (a == Layout.Alignment.ALIGN_CENTER) {
7411             /*
7412              * Keep centered if possible, or, if it is too wide to fit,
7413              * keep leading edge in view.
7414              */
7415 
7416             int left = (int) Math.floor(layout.getLineLeft(line));
7417             int right = (int) Math.ceil(layout.getLineRight(line));
7418 
7419             if (right - left < hspace) {
7420                 scrollx = (right + left) / 2 - hspace / 2;
7421             } else {
7422                 if (dir < 0) {
7423                     scrollx = right - hspace;
7424                 } else {
7425                     scrollx = left;
7426                 }
7427             }
7428         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7429             int right = (int) Math.ceil(layout.getLineRight(line));
7430             scrollx = right - hspace;
7431         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7432             scrollx = (int) Math.floor(layout.getLineLeft(line));
7433         }
7434 
7435         if (ht < vspace) {
7436             scrolly = 0;
7437         } else {
7438             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7439                 scrolly = ht - vspace;
7440             } else {
7441                 scrolly = 0;
7442             }
7443         }
7444 
7445         if (scrollx != mScrollX || scrolly != mScrollY) {
7446             scrollTo(scrollx, scrolly);
7447             return true;
7448         } else {
7449             return false;
7450         }
7451     }
7452 
7453     /**
7454      * Move the point, specified by the offset, into the view if it is needed.
7455      * This has to be called after layout. Returns true if anything changed.
7456      */
bringPointIntoView(int offset)7457     public boolean bringPointIntoView(int offset) {
7458         if (isLayoutRequested()) {
7459             mDeferScroll = offset;
7460             return false;
7461         }
7462         boolean changed = false;
7463 
7464         Layout layout = isShowingHint() ? mHintLayout: mLayout;
7465 
7466         if (layout == null) return changed;
7467 
7468         int line = layout.getLineForOffset(offset);
7469 
7470         int grav;
7471 
7472         switch (layout.getParagraphAlignment(line)) {
7473             case ALIGN_LEFT:
7474                 grav = 1;
7475                 break;
7476             case ALIGN_RIGHT:
7477                 grav = -1;
7478                 break;
7479             case ALIGN_NORMAL:
7480                 grav = layout.getParagraphDirection(line);
7481                 break;
7482             case ALIGN_OPPOSITE:
7483                 grav = -layout.getParagraphDirection(line);
7484                 break;
7485             case ALIGN_CENTER:
7486             default:
7487                 grav = 0;
7488                 break;
7489         }
7490 
7491         // We only want to clamp the cursor to fit within the layout width
7492         // in left-to-right modes, because in a right to left alignment,
7493         // we want to scroll to keep the line-right on the screen, as other
7494         // lines are likely to have text flush with the right margin, which
7495         // we want to keep visible.
7496         // A better long-term solution would probably be to measure both
7497         // the full line and a blank-trimmed version, and, for example, use
7498         // the latter measurement for centering and right alignment, but for
7499         // the time being we only implement the cursor clamping in left to
7500         // right where it is most likely to be annoying.
7501         final boolean clamped = grav > 0;
7502         // FIXME: Is it okay to truncate this, or should we round?
7503         final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7504         final int top = layout.getLineTop(line);
7505         final int bottom = layout.getLineTop(line + 1);
7506 
7507         int left = (int) Math.floor(layout.getLineLeft(line));
7508         int right = (int) Math.ceil(layout.getLineRight(line));
7509         int ht = layout.getHeight();
7510 
7511         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7512         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7513         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7514             // If cursor has been clamped, make sure we don't scroll.
7515             right = Math.max(x, left + hspace);
7516         }
7517 
7518         int hslack = (bottom - top) / 2;
7519         int vslack = hslack;
7520 
7521         if (vslack > vspace / 4)
7522             vslack = vspace / 4;
7523         if (hslack > hspace / 4)
7524             hslack = hspace / 4;
7525 
7526         int hs = mScrollX;
7527         int vs = mScrollY;
7528 
7529         if (top - vs < vslack)
7530             vs = top - vslack;
7531         if (bottom - vs > vspace - vslack)
7532             vs = bottom - (vspace - vslack);
7533         if (ht - vs < vspace)
7534             vs = ht - vspace;
7535         if (0 - vs > 0)
7536             vs = 0;
7537 
7538         if (grav != 0) {
7539             if (x - hs < hslack) {
7540                 hs = x - hslack;
7541             }
7542             if (x - hs > hspace - hslack) {
7543                 hs = x - (hspace - hslack);
7544             }
7545         }
7546 
7547         if (grav < 0) {
7548             if (left - hs > 0)
7549                 hs = left;
7550             if (right - hs < hspace)
7551                 hs = right - hspace;
7552         } else if (grav > 0) {
7553             if (right - hs < hspace)
7554                 hs = right - hspace;
7555             if (left - hs > 0)
7556                 hs = left;
7557         } else /* grav == 0 */ {
7558             if (right - left <= hspace) {
7559                 /*
7560                  * If the entire text fits, center it exactly.
7561                  */
7562                 hs = left - (hspace - (right - left)) / 2;
7563             } else if (x > right - hslack) {
7564                 /*
7565                  * If we are near the right edge, keep the right edge
7566                  * at the edge of the view.
7567                  */
7568                 hs = right - hspace;
7569             } else if (x < left + hslack) {
7570                 /*
7571                  * If we are near the left edge, keep the left edge
7572                  * at the edge of the view.
7573                  */
7574                 hs = left;
7575             } else if (left > hs) {
7576                 /*
7577                  * Is there whitespace visible at the left?  Fix it if so.
7578                  */
7579                 hs = left;
7580             } else if (right < hs + hspace) {
7581                 /*
7582                  * Is there whitespace visible at the right?  Fix it if so.
7583                  */
7584                 hs = right - hspace;
7585             } else {
7586                 /*
7587                  * Otherwise, float as needed.
7588                  */
7589                 if (x - hs < hslack) {
7590                     hs = x - hslack;
7591                 }
7592                 if (x - hs > hspace - hslack) {
7593                     hs = x - (hspace - hslack);
7594                 }
7595             }
7596         }
7597 
7598         if (hs != mScrollX || vs != mScrollY) {
7599             if (mScroller == null) {
7600                 scrollTo(hs, vs);
7601             } else {
7602                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7603                 int dx = hs - mScrollX;
7604                 int dy = vs - mScrollY;
7605 
7606                 if (duration > ANIMATED_SCROLL_GAP) {
7607                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7608                     awakenScrollBars(mScroller.getDuration());
7609                     invalidate();
7610                 } else {
7611                     if (!mScroller.isFinished()) {
7612                         mScroller.abortAnimation();
7613                     }
7614 
7615                     scrollBy(dx, dy);
7616                 }
7617 
7618                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7619             }
7620 
7621             changed = true;
7622         }
7623 
7624         if (isFocused()) {
7625             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7626             // requestRectangleOnScreen() is in terms of content coordinates.
7627 
7628             // The offsets here are to ensure the rectangle we are using is
7629             // within our view bounds, in case the cursor is on the far left
7630             // or right.  If it isn't withing the bounds, then this request
7631             // will be ignored.
7632             if (mTempRect == null) mTempRect = new Rect();
7633             mTempRect.set(x - 2, top, x + 2, bottom);
7634             getInterestingRect(mTempRect, line);
7635             mTempRect.offset(mScrollX, mScrollY);
7636 
7637             if (requestRectangleOnScreen(mTempRect)) {
7638                 changed = true;
7639             }
7640         }
7641 
7642         return changed;
7643     }
7644 
7645     /**
7646      * Move the cursor, if needed, so that it is at an offset that is visible
7647      * to the user.  This will not move the cursor if it represents more than
7648      * one character (a selection range).  This will only work if the
7649      * TextView contains spannable text; otherwise it will do nothing.
7650      *
7651      * @return True if the cursor was actually moved, false otherwise.
7652      */
moveCursorToVisibleOffset()7653     public boolean moveCursorToVisibleOffset() {
7654         if (!(mText instanceof Spannable)) {
7655             return false;
7656         }
7657         int start = getSelectionStart();
7658         int end = getSelectionEnd();
7659         if (start != end) {
7660             return false;
7661         }
7662 
7663         // First: make sure the line is visible on screen:
7664 
7665         int line = mLayout.getLineForOffset(start);
7666 
7667         final int top = mLayout.getLineTop(line);
7668         final int bottom = mLayout.getLineTop(line + 1);
7669         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7670         int vslack = (bottom - top) / 2;
7671         if (vslack > vspace / 4)
7672             vslack = vspace / 4;
7673         final int vs = mScrollY;
7674 
7675         if (top < (vs+vslack)) {
7676             line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7677         } else if (bottom > (vspace+vs-vslack)) {
7678             line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7679         }
7680 
7681         // Next: make sure the character is visible on screen:
7682 
7683         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7684         final int hs = mScrollX;
7685         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7686         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7687 
7688         // line might contain bidirectional text
7689         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7690         final int highChar = leftChar > rightChar ? leftChar : rightChar;
7691 
7692         int newStart = start;
7693         if (newStart < lowChar) {
7694             newStart = lowChar;
7695         } else if (newStart > highChar) {
7696             newStart = highChar;
7697         }
7698 
7699         if (newStart != start) {
7700             Selection.setSelection((Spannable)mText, newStart);
7701             return true;
7702         }
7703 
7704         return false;
7705     }
7706 
7707     @Override
computeScroll()7708     public void computeScroll() {
7709         if (mScroller != null) {
7710             if (mScroller.computeScrollOffset()) {
7711                 mScrollX = mScroller.getCurrX();
7712                 mScrollY = mScroller.getCurrY();
7713                 invalidateParentCaches();
7714                 postInvalidate();  // So we draw again
7715             }
7716         }
7717     }
7718 
getInterestingRect(Rect r, int line)7719     private void getInterestingRect(Rect r, int line) {
7720         convertFromViewportToContentCoordinates(r);
7721 
7722         // Rectangle can can be expanded on first and last line to take
7723         // padding into account.
7724         // TODO Take left/right padding into account too?
7725         if (line == 0) r.top -= getExtendedPaddingTop();
7726         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7727     }
7728 
convertFromViewportToContentCoordinates(Rect r)7729     private void convertFromViewportToContentCoordinates(Rect r) {
7730         final int horizontalOffset = viewportToContentHorizontalOffset();
7731         r.left += horizontalOffset;
7732         r.right += horizontalOffset;
7733 
7734         final int verticalOffset = viewportToContentVerticalOffset();
7735         r.top += verticalOffset;
7736         r.bottom += verticalOffset;
7737     }
7738 
viewportToContentHorizontalOffset()7739     int viewportToContentHorizontalOffset() {
7740         return getCompoundPaddingLeft() - mScrollX;
7741     }
7742 
viewportToContentVerticalOffset()7743     int viewportToContentVerticalOffset() {
7744         int offset = getExtendedPaddingTop() - mScrollY;
7745         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7746             offset += getVerticalOffset(false);
7747         }
7748         return offset;
7749     }
7750 
7751     @Override
debug(int depth)7752     public void debug(int depth) {
7753         super.debug(depth);
7754 
7755         String output = debugIndent(depth);
7756         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7757                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7758                 + "} ";
7759 
7760         if (mText != null) {
7761 
7762             output += "mText=\"" + mText + "\" ";
7763             if (mLayout != null) {
7764                 output += "mLayout width=" + mLayout.getWidth()
7765                         + " height=" + mLayout.getHeight();
7766             }
7767         } else {
7768             output += "mText=NULL";
7769         }
7770         Log.d(VIEW_LOG_TAG, output);
7771     }
7772 
7773     /**
7774      * Convenience for {@link Selection#getSelectionStart}.
7775      */
7776     @ViewDebug.ExportedProperty(category = "text")
getSelectionStart()7777     public int getSelectionStart() {
7778         return Selection.getSelectionStart(getText());
7779     }
7780 
7781     /**
7782      * Convenience for {@link Selection#getSelectionEnd}.
7783      */
7784     @ViewDebug.ExportedProperty(category = "text")
getSelectionEnd()7785     public int getSelectionEnd() {
7786         return Selection.getSelectionEnd(getText());
7787     }
7788 
7789     /**
7790      * Return true iff there is a selection inside this text view.
7791      */
hasSelection()7792     public boolean hasSelection() {
7793         final int selectionStart = getSelectionStart();
7794         final int selectionEnd = getSelectionEnd();
7795 
7796         return selectionStart >= 0 && selectionStart != selectionEnd;
7797     }
7798 
getSelectedText()7799     String getSelectedText() {
7800         if (!hasSelection()) {
7801             return null;
7802         }
7803 
7804         final int start = getSelectionStart();
7805         final int end = getSelectionEnd();
7806         return String.valueOf(
7807                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
7808     }
7809 
7810     /**
7811      * Sets the properties of this field (lines, horizontally scrolling,
7812      * transformation method) to be for a single-line input.
7813      *
7814      * @attr ref android.R.styleable#TextView_singleLine
7815      */
setSingleLine()7816     public void setSingleLine() {
7817         setSingleLine(true);
7818     }
7819 
7820     /**
7821      * Sets the properties of this field to transform input to ALL CAPS
7822      * display. This may use a "small caps" formatting if available.
7823      * This setting will be ignored if this field is editable or selectable.
7824      *
7825      * This call replaces the current transformation method. Disabling this
7826      * will not necessarily restore the previous behavior from before this
7827      * was enabled.
7828      *
7829      * @see #setTransformationMethod(TransformationMethod)
7830      * @attr ref android.R.styleable#TextView_textAllCaps
7831      */
setAllCaps(boolean allCaps)7832     public void setAllCaps(boolean allCaps) {
7833         if (allCaps) {
7834             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7835         } else {
7836             setTransformationMethod(null);
7837         }
7838     }
7839 
7840     /**
7841      * If true, sets the properties of this field (number of lines, horizontally scrolling,
7842      * transformation method) to be for a single-line input; if false, restores these to the default
7843      * conditions.
7844      *
7845      * Note that the default conditions are not necessarily those that were in effect prior this
7846      * method, and you may want to reset these properties to your custom values.
7847      *
7848      * @attr ref android.R.styleable#TextView_singleLine
7849      */
7850     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)7851     public void setSingleLine(boolean singleLine) {
7852         // Could be used, but may break backward compatibility.
7853         // if (mSingleLine == singleLine) return;
7854         setInputTypeSingleLine(singleLine);
7855         applySingleLine(singleLine, true, true);
7856     }
7857 
7858     /**
7859      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7860      * @param singleLine
7861      */
setInputTypeSingleLine(boolean singleLine)7862     private void setInputTypeSingleLine(boolean singleLine) {
7863         if (mEditor != null &&
7864                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7865             if (singleLine) {
7866                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7867             } else {
7868                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7869             }
7870         }
7871     }
7872 
applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)7873     private void applySingleLine(boolean singleLine, boolean applyTransformation,
7874             boolean changeMaxLines) {
7875         mSingleLine = singleLine;
7876         if (singleLine) {
7877             setLines(1);
7878             setHorizontallyScrolling(true);
7879             if (applyTransformation) {
7880                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7881             }
7882         } else {
7883             if (changeMaxLines) {
7884                 setMaxLines(Integer.MAX_VALUE);
7885             }
7886             setHorizontallyScrolling(false);
7887             if (applyTransformation) {
7888                 setTransformationMethod(null);
7889             }
7890         }
7891     }
7892 
7893     /**
7894      * Causes words in the text that are longer than the view's width
7895      * to be ellipsized instead of broken in the middle.  You may also
7896      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7897      * to constrain the text to a single line.  Use <code>null</code>
7898      * to turn off ellipsizing.
7899      *
7900      * If {@link #setMaxLines} has been used to set two or more lines,
7901      * only {@link android.text.TextUtils.TruncateAt#END} and
7902      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7903      * (other ellipsizing types will not do anything).
7904      *
7905      * @attr ref android.R.styleable#TextView_ellipsize
7906      */
setEllipsize(TextUtils.TruncateAt where)7907     public void setEllipsize(TextUtils.TruncateAt where) {
7908         // TruncateAt is an enum. != comparison is ok between these singleton objects.
7909         if (mEllipsize != where) {
7910             mEllipsize = where;
7911 
7912             if (mLayout != null) {
7913                 nullLayouts();
7914                 requestLayout();
7915                 invalidate();
7916             }
7917         }
7918     }
7919 
7920     /**
7921      * Sets how many times to repeat the marquee animation. Only applied if the
7922      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7923      *
7924      * @see #getMarqueeRepeatLimit()
7925      *
7926      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7927      */
setMarqueeRepeatLimit(int marqueeLimit)7928     public void setMarqueeRepeatLimit(int marqueeLimit) {
7929         mMarqueeRepeatLimit = marqueeLimit;
7930     }
7931 
7932     /**
7933      * Gets the number of times the marquee animation is repeated. Only meaningful if the
7934      * TextView has marquee enabled.
7935      *
7936      * @return the number of times the marquee animation is repeated. -1 if the animation
7937      * repeats indefinitely
7938      *
7939      * @see #setMarqueeRepeatLimit(int)
7940      *
7941      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7942      */
getMarqueeRepeatLimit()7943     public int getMarqueeRepeatLimit() {
7944         return mMarqueeRepeatLimit;
7945     }
7946 
7947     /**
7948      * Returns where, if anywhere, words that are longer than the view
7949      * is wide should be ellipsized.
7950      */
7951     @ViewDebug.ExportedProperty
getEllipsize()7952     public TextUtils.TruncateAt getEllipsize() {
7953         return mEllipsize;
7954     }
7955 
7956     /**
7957      * Set the TextView so that when it takes focus, all the text is
7958      * selected.
7959      *
7960      * @attr ref android.R.styleable#TextView_selectAllOnFocus
7961      */
7962     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)7963     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7964         createEditorIfNeeded();
7965         mEditor.mSelectAllOnFocus = selectAllOnFocus;
7966 
7967         if (selectAllOnFocus && !(mText instanceof Spannable)) {
7968             setText(mText, BufferType.SPANNABLE);
7969         }
7970     }
7971 
7972     /**
7973      * Set whether the cursor is visible. The default is true. Note that this property only
7974      * makes sense for editable TextView.
7975      *
7976      * @see #isCursorVisible()
7977      *
7978      * @attr ref android.R.styleable#TextView_cursorVisible
7979      */
7980     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)7981     public void setCursorVisible(boolean visible) {
7982         if (visible && mEditor == null) return; // visible is the default value with no edit data
7983         createEditorIfNeeded();
7984         if (mEditor.mCursorVisible != visible) {
7985             mEditor.mCursorVisible = visible;
7986             invalidate();
7987 
7988             mEditor.makeBlink();
7989 
7990             // InsertionPointCursorController depends on mCursorVisible
7991             mEditor.prepareCursorControllers();
7992         }
7993     }
7994 
7995     /**
7996      * @return whether or not the cursor is visible (assuming this TextView is editable)
7997      *
7998      * @see #setCursorVisible(boolean)
7999      *
8000      * @attr ref android.R.styleable#TextView_cursorVisible
8001      */
isCursorVisible()8002     public boolean isCursorVisible() {
8003         // true is the default value
8004         return mEditor == null ? true : mEditor.mCursorVisible;
8005     }
8006 
canMarquee()8007     private boolean canMarquee() {
8008         int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
8009         return width > 0 && (mLayout.getLineWidth(0) > width ||
8010                 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
8011                         mSavedMarqueeModeLayout.getLineWidth(0) > width));
8012     }
8013 
startMarquee()8014     private void startMarquee() {
8015         // Do not ellipsize EditText
8016         if (getKeyListener() != null) return;
8017 
8018         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8019             return;
8020         }
8021 
8022         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
8023                 getLineCount() == 1 && canMarquee()) {
8024 
8025             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8026                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8027                 final Layout tmp = mLayout;
8028                 mLayout = mSavedMarqueeModeLayout;
8029                 mSavedMarqueeModeLayout = tmp;
8030                 setHorizontalFadingEdgeEnabled(true);
8031                 requestLayout();
8032                 invalidate();
8033             }
8034 
8035             if (mMarquee == null) mMarquee = new Marquee(this);
8036             mMarquee.start(mMarqueeRepeatLimit);
8037         }
8038     }
8039 
stopMarquee()8040     private void stopMarquee() {
8041         if (mMarquee != null && !mMarquee.isStopped()) {
8042             mMarquee.stop();
8043         }
8044 
8045         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8046             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8047             final Layout tmp = mSavedMarqueeModeLayout;
8048             mSavedMarqueeModeLayout = mLayout;
8049             mLayout = tmp;
8050             setHorizontalFadingEdgeEnabled(false);
8051             requestLayout();
8052             invalidate();
8053         }
8054     }
8055 
startStopMarquee(boolean start)8056     private void startStopMarquee(boolean start) {
8057         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8058             if (start) {
8059                 startMarquee();
8060             } else {
8061                 stopMarquee();
8062             }
8063         }
8064     }
8065 
8066     /**
8067      * This method is called when the text is changed, in case any subclasses
8068      * would like to know.
8069      *
8070      * Within <code>text</code>, the <code>lengthAfter</code> characters
8071      * beginning at <code>start</code> have just replaced old text that had
8072      * length <code>lengthBefore</code>. It is an error to attempt to make
8073      * changes to <code>text</code> from this callback.
8074      *
8075      * @param text The text the TextView is displaying
8076      * @param start The offset of the start of the range of the text that was
8077      * modified
8078      * @param lengthBefore The length of the former text that has been replaced
8079      * @param lengthAfter The length of the replacement modified text
8080      */
onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)8081     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
8082         // intentionally empty, template pattern method can be overridden by subclasses
8083     }
8084 
8085     /**
8086      * This method is called when the selection has changed, in case any
8087      * subclasses would like to know.
8088      *
8089      * @param selStart The new selection start location.
8090      * @param selEnd The new selection end location.
8091      */
onSelectionChanged(int selStart, int selEnd)8092     protected void onSelectionChanged(int selStart, int selEnd) {
8093         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
8094     }
8095 
8096     /**
8097      * Adds a TextWatcher to the list of those whose methods are called
8098      * whenever this TextView's text changes.
8099      * <p>
8100      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
8101      * not called after {@link #setText} calls.  Now, doing {@link #setText}
8102      * if there are any text changed listeners forces the buffer type to
8103      * Editable if it would not otherwise be and does call this method.
8104      */
addTextChangedListener(TextWatcher watcher)8105     public void addTextChangedListener(TextWatcher watcher) {
8106         if (mListeners == null) {
8107             mListeners = new ArrayList<TextWatcher>();
8108         }
8109 
8110         mListeners.add(watcher);
8111     }
8112 
8113     /**
8114      * Removes the specified TextWatcher from the list of those whose
8115      * methods are called
8116      * whenever this TextView's text changes.
8117      */
removeTextChangedListener(TextWatcher watcher)8118     public void removeTextChangedListener(TextWatcher watcher) {
8119         if (mListeners != null) {
8120             int i = mListeners.indexOf(watcher);
8121 
8122             if (i >= 0) {
8123                 mListeners.remove(i);
8124             }
8125         }
8126     }
8127 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)8128     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
8129         if (mListeners != null) {
8130             final ArrayList<TextWatcher> list = mListeners;
8131             final int count = list.size();
8132             for (int i = 0; i < count; i++) {
8133                 list.get(i).beforeTextChanged(text, start, before, after);
8134             }
8135         }
8136 
8137         // The spans that are inside or intersect the modified region no longer make sense
8138         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8139         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
8140     }
8141 
8142     // Removes all spans that are inside or actually overlap the start..end range
removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)8143     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
8144         if (!(mText instanceof Editable)) return;
8145         Editable text = (Editable) mText;
8146 
8147         T[] spans = text.getSpans(start, end, type);
8148         final int length = spans.length;
8149         for (int i = 0; i < length; i++) {
8150             final int spanStart = text.getSpanStart(spans[i]);
8151             final int spanEnd = text.getSpanEnd(spans[i]);
8152             if (spanEnd == start || spanStart == end) break;
8153             text.removeSpan(spans[i]);
8154         }
8155     }
8156 
removeAdjacentSuggestionSpans(final int pos)8157     void removeAdjacentSuggestionSpans(final int pos) {
8158         if (!(mText instanceof Editable)) return;
8159         final Editable text = (Editable) mText;
8160 
8161         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8162         final int length = spans.length;
8163         for (int i = 0; i < length; i++) {
8164             final int spanStart = text.getSpanStart(spans[i]);
8165             final int spanEnd = text.getSpanEnd(spans[i]);
8166             if (spanEnd == pos || spanStart == pos) {
8167                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8168                     text.removeSpan(spans[i]);
8169                 }
8170             }
8171         }
8172     }
8173 
8174     /**
8175      * Not private so it can be called from an inner class without going
8176      * through a thunk.
8177      */
sendOnTextChanged(CharSequence text, int start, int before, int after)8178     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
8179         if (mListeners != null) {
8180             final ArrayList<TextWatcher> list = mListeners;
8181             final int count = list.size();
8182             for (int i = 0; i < count; i++) {
8183                 list.get(i).onTextChanged(text, start, before, after);
8184             }
8185         }
8186 
8187         if (mEditor != null) mEditor.sendOnTextChanged(start, after);
8188     }
8189 
8190     /**
8191      * Not private so it can be called from an inner class without going
8192      * through a thunk.
8193      */
sendAfterTextChanged(Editable text)8194     void sendAfterTextChanged(Editable text) {
8195         if (mListeners != null) {
8196             final ArrayList<TextWatcher> list = mListeners;
8197             final int count = list.size();
8198             for (int i = 0; i < count; i++) {
8199                 list.get(i).afterTextChanged(text);
8200             }
8201         }
8202         hideErrorIfUnchanged();
8203     }
8204 
updateAfterEdit()8205     void updateAfterEdit() {
8206         invalidate();
8207         int curs = getSelectionStart();
8208 
8209         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8210             registerForPreDraw();
8211         }
8212 
8213         checkForResize();
8214 
8215         if (curs >= 0) {
8216             mHighlightPathBogus = true;
8217             if (mEditor != null) mEditor.makeBlink();
8218             bringPointIntoView(curs);
8219         }
8220     }
8221 
8222     /**
8223      * Not private so it can be called from an inner class without going
8224      * through a thunk.
8225      */
handleTextChanged(CharSequence buffer, int start, int before, int after)8226     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8227         sLastCutCopyOrTextChangedTime = 0;
8228 
8229         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8230         if (ims == null || ims.mBatchEditNesting == 0) {
8231             updateAfterEdit();
8232         }
8233         if (ims != null) {
8234             ims.mContentChanged = true;
8235             if (ims.mChangedStart < 0) {
8236                 ims.mChangedStart = start;
8237                 ims.mChangedEnd = start+before;
8238             } else {
8239                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8240                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8241             }
8242             ims.mChangedDelta += after-before;
8243         }
8244         resetErrorChangedFlag();
8245         sendOnTextChanged(buffer, start, before, after);
8246         onTextChanged(buffer, start, before, after);
8247     }
8248 
8249     /**
8250      * Not private so it can be called from an inner class without going
8251      * through a thunk.
8252      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)8253     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8254         // XXX Make the start and end move together if this ends up
8255         // spending too much time invalidating.
8256 
8257         boolean selChanged = false;
8258         int newSelStart=-1, newSelEnd=-1;
8259 
8260         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8261 
8262         if (what == Selection.SELECTION_END) {
8263             selChanged = true;
8264             newSelEnd = newStart;
8265 
8266             if (oldStart >= 0 || newStart >= 0) {
8267                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8268                 checkForResize();
8269                 registerForPreDraw();
8270                 if (mEditor != null) mEditor.makeBlink();
8271             }
8272         }
8273 
8274         if (what == Selection.SELECTION_START) {
8275             selChanged = true;
8276             newSelStart = newStart;
8277 
8278             if (oldStart >= 0 || newStart >= 0) {
8279                 int end = Selection.getSelectionEnd(buf);
8280                 invalidateCursor(end, oldStart, newStart);
8281             }
8282         }
8283 
8284         if (selChanged) {
8285             mHighlightPathBogus = true;
8286             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8287 
8288             if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8289                 if (newSelStart < 0) {
8290                     newSelStart = Selection.getSelectionStart(buf);
8291                 }
8292                 if (newSelEnd < 0) {
8293                     newSelEnd = Selection.getSelectionEnd(buf);
8294                 }
8295 
8296                 if (mEditor != null) {
8297                     mEditor.refreshTextActionMode();
8298                     if (!hasSelection() && mEditor.mTextActionMode == null && hasTransientState()) {
8299                         // User generated selection has been removed.
8300                         setHasTransientState(false);
8301                     }
8302                 }
8303                 onSelectionChanged(newSelStart, newSelEnd);
8304             }
8305         }
8306 
8307         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8308                 what instanceof CharacterStyle) {
8309             if (ims == null || ims.mBatchEditNesting == 0) {
8310                 invalidate();
8311                 mHighlightPathBogus = true;
8312                 checkForResize();
8313             } else {
8314                 ims.mContentChanged = true;
8315             }
8316             if (mEditor != null) {
8317                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8318                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8319                 mEditor.invalidateHandlesAndActionMode();
8320             }
8321         }
8322 
8323         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8324             mHighlightPathBogus = true;
8325             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8326                 ims.mSelectionModeChanged = true;
8327             }
8328 
8329             if (Selection.getSelectionStart(buf) >= 0) {
8330                 if (ims == null || ims.mBatchEditNesting == 0) {
8331                     invalidateCursor();
8332                 } else {
8333                     ims.mCursorChanged = true;
8334                 }
8335             }
8336         }
8337 
8338         if (what instanceof ParcelableSpan) {
8339             // If this is a span that can be sent to a remote process,
8340             // the current extract editor would be interested in it.
8341             if (ims != null && ims.mExtractedTextRequest != null) {
8342                 if (ims.mBatchEditNesting != 0) {
8343                     if (oldStart >= 0) {
8344                         if (ims.mChangedStart > oldStart) {
8345                             ims.mChangedStart = oldStart;
8346                         }
8347                         if (ims.mChangedStart > oldEnd) {
8348                             ims.mChangedStart = oldEnd;
8349                         }
8350                     }
8351                     if (newStart >= 0) {
8352                         if (ims.mChangedStart > newStart) {
8353                             ims.mChangedStart = newStart;
8354                         }
8355                         if (ims.mChangedStart > newEnd) {
8356                             ims.mChangedStart = newEnd;
8357                         }
8358                     }
8359                 } else {
8360                     if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8361                             + oldStart + "-" + oldEnd + ","
8362                             + newStart + "-" + newEnd + " " + what);
8363                     ims.mContentChanged = true;
8364                 }
8365             }
8366         }
8367 
8368         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8369                 what instanceof SpellCheckSpan) {
8370             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8371         }
8372     }
8373 
8374     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)8375     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8376         if (isTemporarilyDetached()) {
8377             // If we are temporarily in the detach state, then do nothing.
8378             super.onFocusChanged(focused, direction, previouslyFocusedRect);
8379             return;
8380         }
8381 
8382         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8383 
8384         if (focused) {
8385             if (mText instanceof Spannable) {
8386                 Spannable sp = (Spannable) mText;
8387                 MetaKeyKeyListener.resetMetaState(sp);
8388             }
8389         }
8390 
8391         startStopMarquee(focused);
8392 
8393         if (mTransformation != null) {
8394             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8395         }
8396 
8397         super.onFocusChanged(focused, direction, previouslyFocusedRect);
8398     }
8399 
8400     @Override
onWindowFocusChanged(boolean hasWindowFocus)8401     public void onWindowFocusChanged(boolean hasWindowFocus) {
8402         super.onWindowFocusChanged(hasWindowFocus);
8403 
8404         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8405 
8406         startStopMarquee(hasWindowFocus);
8407     }
8408 
8409     @Override
onVisibilityChanged(View changedView, int visibility)8410     protected void onVisibilityChanged(View changedView, int visibility) {
8411         super.onVisibilityChanged(changedView, visibility);
8412         if (mEditor != null && visibility != VISIBLE) {
8413             mEditor.hideCursorAndSpanControllers();
8414             stopTextActionMode();
8415         }
8416     }
8417 
8418     /**
8419      * Use {@link BaseInputConnection#removeComposingSpans
8420      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8421      * state from this text view.
8422      */
clearComposingText()8423     public void clearComposingText() {
8424         if (mText instanceof Spannable) {
8425             BaseInputConnection.removeComposingSpans((Spannable)mText);
8426         }
8427     }
8428 
8429     @Override
setSelected(boolean selected)8430     public void setSelected(boolean selected) {
8431         boolean wasSelected = isSelected();
8432 
8433         super.setSelected(selected);
8434 
8435         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8436             if (selected) {
8437                 startMarquee();
8438             } else {
8439                 stopMarquee();
8440             }
8441         }
8442     }
8443 
8444     @Override
onTouchEvent(MotionEvent event)8445     public boolean onTouchEvent(MotionEvent event) {
8446         final int action = event.getActionMasked();
8447         if (mEditor != null) {
8448             mEditor.onTouchEvent(event);
8449 
8450             if (mEditor.mSelectionModifierCursorController != null &&
8451                     mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8452                 return true;
8453             }
8454         }
8455 
8456         final boolean superResult = super.onTouchEvent(event);
8457 
8458         /*
8459          * Don't handle the release after a long press, because it will move the selection away from
8460          * whatever the menu action was trying to affect. If the long press should have triggered an
8461          * insertion action mode, we can now actually show it.
8462          */
8463         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8464             mEditor.mDiscardNextActionUp = false;
8465 
8466             if (mEditor.mIsInsertionActionModeStartPending) {
8467                 mEditor.startInsertionActionMode();
8468                 mEditor.mIsInsertionActionModeStartPending = false;
8469             }
8470             return superResult;
8471         }
8472 
8473         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8474                 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8475 
8476          if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8477                 && mText instanceof Spannable && mLayout != null) {
8478             boolean handled = false;
8479 
8480             if (mMovement != null) {
8481                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8482             }
8483 
8484             final boolean textIsSelectable = isTextSelectable();
8485             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8486                 // The LinkMovementMethod which should handle taps on links has not been installed
8487                 // on non editable text that support text selection.
8488                 // We reproduce its behavior here to open links for these.
8489                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8490                         getSelectionEnd(), ClickableSpan.class);
8491 
8492                 if (links.length > 0) {
8493                     links[0].onClick(this);
8494                     handled = true;
8495                 }
8496             }
8497 
8498             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8499                 // Show the IME, except when selecting in read-only text.
8500                 final InputMethodManager imm = InputMethodManager.peekInstance();
8501                 viewClicked(imm);
8502                 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8503                     handled |= imm != null && imm.showSoftInput(this, 0);
8504                 }
8505 
8506                 // The above condition ensures that the mEditor is not null
8507                 mEditor.onTouchUpEvent(event);
8508 
8509                 handled = true;
8510             }
8511 
8512             if (handled) {
8513                 return true;
8514             }
8515         }
8516 
8517         return superResult;
8518     }
8519 
8520     @Override
onGenericMotionEvent(MotionEvent event)8521     public boolean onGenericMotionEvent(MotionEvent event) {
8522         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8523             try {
8524                 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8525                     return true;
8526                 }
8527             } catch (AbstractMethodError ex) {
8528                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8529                 // Ignore its absence in case third party applications implemented the
8530                 // interface directly.
8531             }
8532         }
8533         return super.onGenericMotionEvent(event);
8534     }
8535 
8536     @Override
onCreateContextMenu(ContextMenu menu)8537     protected void onCreateContextMenu(ContextMenu menu) {
8538         if (mEditor != null) {
8539             mEditor.onCreateContextMenu(menu);
8540         }
8541     }
8542 
8543     @Override
showContextMenu()8544     public boolean showContextMenu() {
8545         if (mEditor != null) {
8546             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
8547         }
8548         return super.showContextMenu();
8549     }
8550 
8551     @Override
showContextMenu(float x, float y)8552     public boolean showContextMenu(float x, float y) {
8553         if (mEditor != null) {
8554             mEditor.setContextMenuAnchor(x, y);
8555         }
8556         return super.showContextMenu(x, y);
8557     }
8558 
8559     /**
8560      * @return True iff this TextView contains a text that can be edited, or if this is
8561      * a selectable TextView.
8562      */
isTextEditable()8563     boolean isTextEditable() {
8564         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8565     }
8566 
8567     /**
8568      * Returns true, only while processing a touch gesture, if the initial
8569      * touch down event caused focus to move to the text view and as a result
8570      * its selection changed.  Only valid while processing the touch gesture
8571      * of interest, in an editable text view.
8572      */
didTouchFocusSelect()8573     public boolean didTouchFocusSelect() {
8574         return mEditor != null && mEditor.mTouchFocusSelected;
8575     }
8576 
8577     @Override
cancelLongPress()8578     public void cancelLongPress() {
8579         super.cancelLongPress();
8580         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8581     }
8582 
8583     @Override
onTrackballEvent(MotionEvent event)8584     public boolean onTrackballEvent(MotionEvent event) {
8585         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8586             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8587                 return true;
8588             }
8589         }
8590 
8591         return super.onTrackballEvent(event);
8592     }
8593 
setScroller(Scroller s)8594     public void setScroller(Scroller s) {
8595         mScroller = s;
8596     }
8597 
8598     @Override
getLeftFadingEdgeStrength()8599     protected float getLeftFadingEdgeStrength() {
8600         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
8601             final Marquee marquee = mMarquee;
8602             if (marquee.shouldDrawLeftFade()) {
8603                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
8604             } else {
8605                 return 0.0f;
8606             }
8607         } else if (getLineCount() == 1) {
8608             final float lineLeft = getLayout().getLineLeft(0);
8609             if(lineLeft > mScrollX) return 0.0f;
8610             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
8611         }
8612         return super.getLeftFadingEdgeStrength();
8613     }
8614 
8615     @Override
getRightFadingEdgeStrength()8616     protected float getRightFadingEdgeStrength() {
8617         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
8618             final Marquee marquee = mMarquee;
8619             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
8620         } else if (getLineCount() == 1) {
8621             final float rightEdge = mScrollX + (getWidth() - getCompoundPaddingLeft() -
8622                     getCompoundPaddingRight());
8623             final float lineRight = getLayout().getLineRight(0);
8624             if(lineRight < rightEdge) return 0.0f;
8625             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
8626         }
8627         return super.getRightFadingEdgeStrength();
8628     }
8629 
8630     /**
8631      * Calculates the fading edge strength as the ratio of the distance between two
8632      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
8633      * value for the distance calculation.
8634      *
8635      * @param position1 A horizontal position.
8636      * @param position2 A horizontal position.
8637      * @return Fading edge strength between [0.0f, 1.0f].
8638      */
8639     @FloatRange(from=0.0, to=1.0)
getHorizontalFadingEdgeStrength(float position1, float position2)8640     private final float getHorizontalFadingEdgeStrength(float position1, float position2) {
8641         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
8642         if(horizontalFadingEdgeLength == 0) return 0.0f;
8643         final float diff = Math.abs(position1 - position2);
8644         if(diff > horizontalFadingEdgeLength) return 1.0f;
8645         return diff / horizontalFadingEdgeLength;
8646     }
8647 
isMarqueeFadeEnabled()8648     private final boolean isMarqueeFadeEnabled() {
8649         return mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8650                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8651     }
8652 
8653     @Override
computeHorizontalScrollRange()8654     protected int computeHorizontalScrollRange() {
8655         if (mLayout != null) {
8656             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8657                     (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8658         }
8659 
8660         return super.computeHorizontalScrollRange();
8661     }
8662 
8663     @Override
computeVerticalScrollRange()8664     protected int computeVerticalScrollRange() {
8665         if (mLayout != null)
8666             return mLayout.getHeight();
8667 
8668         return super.computeVerticalScrollRange();
8669     }
8670 
8671     @Override
computeVerticalScrollExtent()8672     protected int computeVerticalScrollExtent() {
8673         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8674     }
8675 
8676     @Override
findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)8677     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8678         super.findViewsWithText(outViews, searched, flags);
8679         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8680                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8681             String searchedLowerCase = searched.toString().toLowerCase();
8682             String textLowerCase = mText.toString().toLowerCase();
8683             if (textLowerCase.contains(searchedLowerCase)) {
8684                 outViews.add(this);
8685             }
8686         }
8687     }
8688 
8689     public enum BufferType {
8690         NORMAL, SPANNABLE, EDITABLE,
8691     }
8692 
8693     /**
8694      * Returns the TextView_textColor attribute from the TypedArray, if set, or
8695      * the TextAppearance_textColor from the TextView_textAppearance attribute,
8696      * if TextView_textColor was not set directly.
8697      *
8698      * @removed
8699      */
getTextColors(Context context, TypedArray attrs)8700     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8701         if (attrs == null) {
8702             // Preserve behavior prior to removal of this API.
8703             throw new NullPointerException();
8704         }
8705 
8706         // It's not safe to use this method from apps. The parameter 'attrs'
8707         // must have been obtained using the TextView filter array which is not
8708         // available to the SDK. As such, we grab a default TypedArray with the
8709         // right filter instead here.
8710         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8711         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8712         if (colors == null) {
8713             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8714             if (ap != 0) {
8715                 final TypedArray appearance = context.obtainStyledAttributes(
8716                         ap, R.styleable.TextAppearance);
8717                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8718                 appearance.recycle();
8719             }
8720         }
8721         a.recycle();
8722 
8723         return colors;
8724     }
8725 
8726     /**
8727      * Returns the default color from the TextView_textColor attribute from the
8728      * AttributeSet, if set, or the default color from the
8729      * TextAppearance_textColor from the TextView_textAppearance attribute, if
8730      * TextView_textColor was not set directly.
8731      *
8732      * @removed
8733      */
getTextColor(Context context, TypedArray attrs, int def)8734     public static int getTextColor(Context context, TypedArray attrs, int def) {
8735         final ColorStateList colors = getTextColors(context, attrs);
8736         if (colors == null) {
8737             return def;
8738         } else {
8739             return colors.getDefaultColor();
8740         }
8741     }
8742 
8743     @Override
onKeyShortcut(int keyCode, KeyEvent event)8744     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8745         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8746             // Handle Ctrl-only shortcuts.
8747             switch (keyCode) {
8748             case KeyEvent.KEYCODE_A:
8749                 if (canSelectText()) {
8750                     return onTextContextMenuItem(ID_SELECT_ALL);
8751                 }
8752                 break;
8753             case KeyEvent.KEYCODE_Z:
8754                 if (canUndo()) {
8755                     return onTextContextMenuItem(ID_UNDO);
8756                 }
8757                 break;
8758             case KeyEvent.KEYCODE_X:
8759                 if (canCut()) {
8760                     return onTextContextMenuItem(ID_CUT);
8761                 }
8762                 break;
8763             case KeyEvent.KEYCODE_C:
8764                 if (canCopy()) {
8765                     return onTextContextMenuItem(ID_COPY);
8766                 }
8767                 break;
8768             case KeyEvent.KEYCODE_V:
8769                 if (canPaste()) {
8770                     return onTextContextMenuItem(ID_PASTE);
8771                 }
8772                 break;
8773             }
8774         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8775             // Handle Ctrl-Shift shortcuts.
8776             switch (keyCode) {
8777                 case KeyEvent.KEYCODE_Z:
8778                     if (canRedo()) {
8779                         return onTextContextMenuItem(ID_REDO);
8780                     }
8781                     break;
8782                 case KeyEvent.KEYCODE_V:
8783                     if (canPaste()) {
8784                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8785                     }
8786             }
8787         }
8788         return super.onKeyShortcut(keyCode, event);
8789     }
8790 
8791     /**
8792      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8793      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8794      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8795      * sufficient.
8796      */
canSelectText()8797     boolean canSelectText() {
8798         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8799     }
8800 
8801     /**
8802      * Test based on the <i>intrinsic</i> charateristics of the TextView.
8803      * The text must be spannable and the movement method must allow for arbitary selection.
8804      *
8805      * See also {@link #canSelectText()}.
8806      */
textCanBeSelected()8807     boolean textCanBeSelected() {
8808         // prepareCursorController() relies on this method.
8809         // If you change this condition, make sure prepareCursorController is called anywhere
8810         // the value of this condition might be changed.
8811         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8812         return isTextEditable() ||
8813                 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8814     }
8815 
getTextServicesLocale(boolean allowNullLocale)8816     private Locale getTextServicesLocale(boolean allowNullLocale) {
8817         // Start fetching the text services locale asynchronously.
8818         updateTextServicesLocaleAsync();
8819         // If !allowNullLocale and there is no cached text services locale, just return the default
8820         // locale.
8821         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8822                 : mCurrentSpellCheckerLocaleCache;
8823     }
8824 
8825     /**
8826      * This is a temporary method. Future versions may support multi-locale text.
8827      * Caveat: This method may not return the latest text services locale, but this should be
8828      * acceptable and it's more important to make this method asynchronous.
8829      *
8830      * @return The locale that should be used for a word iterator
8831      * in this TextView, based on the current spell checker settings,
8832      * the current IME's locale, or the system default locale.
8833      * Please note that a word iterator in this TextView is different from another word iterator
8834      * used by SpellChecker.java of TextView. This method should be used for the former.
8835      * @hide
8836      */
8837     // TODO: Support multi-locale
8838     // TODO: Update the text services locale immediately after the keyboard locale is switched
8839     // by catching intent of keyboard switch event
getTextServicesLocale()8840     public Locale getTextServicesLocale() {
8841         return getTextServicesLocale(false /* allowNullLocale */);
8842     }
8843 
8844     /**
8845      * @return true if this TextView is specialized for showing and interacting with the extracted
8846      * text in a full-screen input method.
8847      * @hide
8848      */
isInExtractedMode()8849     public boolean isInExtractedMode() {
8850         return false;
8851     }
8852 
8853     /**
8854      * This is a temporary method. Future versions may support multi-locale text.
8855      * Caveat: This method may not return the latest spell checker locale, but this should be
8856      * acceptable and it's more important to make this method asynchronous.
8857      *
8858      * @return The locale that should be used for a spell checker in this TextView,
8859      * based on the current spell checker settings, the current IME's locale, or the system default
8860      * locale.
8861      * @hide
8862      */
getSpellCheckerLocale()8863     public Locale getSpellCheckerLocale() {
8864         return getTextServicesLocale(true /* allowNullLocale */);
8865     }
8866 
updateTextServicesLocaleAsync()8867     private void updateTextServicesLocaleAsync() {
8868         // AsyncTask.execute() uses a serial executor which means we don't have
8869         // to lock around updateTextServicesLocaleLocked() to prevent it from
8870         // being executed n times in parallel.
8871         AsyncTask.execute(new Runnable() {
8872             @Override
8873             public void run() {
8874                 updateTextServicesLocaleLocked();
8875             }
8876         });
8877     }
8878 
updateTextServicesLocaleLocked()8879     private void updateTextServicesLocaleLocked() {
8880         final TextServicesManager textServicesManager = (TextServicesManager)
8881                 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8882         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8883         final Locale locale;
8884         if (subtype != null) {
8885             locale = subtype.getLocaleObject();
8886         } else {
8887             locale = null;
8888         }
8889         mCurrentSpellCheckerLocaleCache = locale;
8890     }
8891 
onLocaleChanged()8892     void onLocaleChanged() {
8893         mEditor.onLocaleChanged();
8894     }
8895 
8896     /**
8897      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8898      * Made available to achieve a consistent behavior.
8899      * @hide
8900      */
getWordIterator()8901     public WordIterator getWordIterator() {
8902         if (mEditor != null) {
8903             return mEditor.getWordIterator();
8904         } else {
8905             return null;
8906         }
8907     }
8908 
8909     /** @hide */
8910     @Override
onPopulateAccessibilityEventInternal(AccessibilityEvent event)8911     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8912         super.onPopulateAccessibilityEventInternal(event);
8913 
8914         final CharSequence text = getTextForAccessibility();
8915         if (!TextUtils.isEmpty(text)) {
8916             event.getText().add(text);
8917         }
8918     }
8919 
8920     /**
8921      * @return true if the user has explicitly allowed accessibility services
8922      * to speak passwords.
8923      */
shouldSpeakPasswordsForAccessibility()8924     private boolean shouldSpeakPasswordsForAccessibility() {
8925         return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8926                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8927                 UserHandle.USER_CURRENT_OR_SELF) == 1);
8928     }
8929 
8930     @Override
getAccessibilityClassName()8931     public CharSequence getAccessibilityClassName() {
8932         return TextView.class.getName();
8933     }
8934 
8935     @Override
onProvideStructure(ViewStructure structure)8936     public void onProvideStructure(ViewStructure structure) {
8937         super.onProvideStructure(structure);
8938         final boolean isPassword = hasPasswordTransformationMethod()
8939                 || isPasswordInputType(getInputType());
8940         if (!isPassword) {
8941             if (mLayout == null) {
8942                 assumeLayout();
8943             }
8944             Layout layout = mLayout;
8945             final int lineCount = layout.getLineCount();
8946             if (lineCount <= 1) {
8947                 // Simple case: this is a single line.
8948                 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8949             } else {
8950                 // Complex case: multi-line, could be scrolled or within a scroll container
8951                 // so some lines are not visible.
8952                 final int[] tmpCords = new int[2];
8953                 getLocationInWindow(tmpCords);
8954                 final int topWindowLocation = tmpCords[1];
8955                 View root = this;
8956                 ViewParent viewParent = getParent();
8957                 while (viewParent instanceof View) {
8958                     root = (View) viewParent;
8959                     viewParent = root.getParent();
8960                 }
8961                 final int windowHeight = root.getHeight();
8962                 final int topLine;
8963                 final int bottomLine;
8964                 if (topWindowLocation >= 0) {
8965                     // The top of the view is fully within its window; start text at line 0.
8966                     topLine = getLineAtCoordinateUnclamped(0);
8967                     bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
8968                 } else {
8969                     // The top of hte window has scrolled off the top of the window; figure out
8970                     // the starting line for this.
8971                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
8972                     bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
8973                 }
8974                 // We want to return some contextual lines above/below the lines that are
8975                 // actually visible.
8976                 int expandedTopLine = topLine - (bottomLine-topLine)/2;
8977                 if (expandedTopLine < 0) {
8978                     expandedTopLine = 0;
8979                 }
8980                 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
8981                 if (expandedBottomLine >= lineCount) {
8982                     expandedBottomLine = lineCount-1;
8983                 }
8984                 // Convert lines into character offsets.
8985                 int expandedTopChar = layout.getLineStart(expandedTopLine);
8986                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
8987                 // Take into account selection -- if there is a selection, we need to expand
8988                 // the text we are returning to include that selection.
8989                 final int selStart = getSelectionStart();
8990                 final int selEnd = getSelectionEnd();
8991                 if (selStart < selEnd) {
8992                     if (selStart < expandedTopChar) {
8993                         expandedTopChar = selStart;
8994                     }
8995                     if (selEnd > expandedBottomChar) {
8996                         expandedBottomChar = selEnd;
8997                     }
8998                 }
8999                 // Get the text and trim it to the range we are reporting.
9000                 CharSequence text = getText();
9001                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9002                     text = text.subSequence(expandedTopChar, expandedBottomChar);
9003                 }
9004                 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
9005                 final int[] lineOffsets = new int[bottomLine-topLine+1];
9006                 final int[] lineBaselines = new int[bottomLine-topLine+1];
9007                 final int baselineOffset = getBaselineOffset();
9008                 for (int i=topLine; i<=bottomLine; i++) {
9009                     lineOffsets[i-topLine] = layout.getLineStart(i);
9010                     lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
9011                 }
9012                 structure.setTextLines(lineOffsets, lineBaselines);
9013             }
9014 
9015             // Extract style information that applies to the TextView as a whole.
9016             int style = 0;
9017             int typefaceStyle = getTypefaceStyle();
9018             if ((typefaceStyle & Typeface.BOLD) != 0) {
9019                 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9020             }
9021             if ((typefaceStyle & Typeface.ITALIC) != 0) {
9022                 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9023             }
9024 
9025             // Global styles can also be set via TextView.setPaintFlags().
9026             int paintFlags = mTextPaint.getFlags();
9027             if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9028                 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9029             }
9030             if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9031                 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9032             }
9033             if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
9034                 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
9035             }
9036 
9037             // TextView does not have its own text background color. A background is either part
9038             // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
9039             structure.setTextStyle(getTextSize(), getCurrentTextColor(),
9040                     AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
9041         }
9042         structure.setHint(getHint());
9043     }
9044 
9045     /** @hide */
9046     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)9047     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
9048         super.onInitializeAccessibilityEventInternal(event);
9049 
9050         final boolean isPassword = hasPasswordTransformationMethod();
9051         event.setPassword(isPassword);
9052 
9053         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
9054             event.setFromIndex(Selection.getSelectionStart(mText));
9055             event.setToIndex(Selection.getSelectionEnd(mText));
9056             event.setItemCount(mText.length());
9057         }
9058     }
9059 
9060     /** @hide */
9061     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)9062     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
9063         super.onInitializeAccessibilityNodeInfoInternal(info);
9064 
9065         final boolean isPassword = hasPasswordTransformationMethod();
9066         info.setPassword(isPassword);
9067         info.setText(getTextForAccessibility());
9068 
9069         if (mBufferType == BufferType.EDITABLE) {
9070             info.setEditable(true);
9071             if (isEnabled()) {
9072                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
9073             }
9074         }
9075 
9076         if (mEditor != null) {
9077             info.setInputType(mEditor.mInputType);
9078 
9079             if (mEditor.mError != null) {
9080                 info.setContentInvalid(true);
9081                 info.setError(mEditor.mError);
9082             }
9083         }
9084 
9085         if (!TextUtils.isEmpty(mText)) {
9086             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
9087             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
9088             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
9089                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
9090                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
9091                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
9092                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
9093             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
9094         }
9095 
9096         if (isFocused()) {
9097             if (canCopy()) {
9098                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
9099             }
9100             if (canPaste()) {
9101                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
9102             }
9103             if (canCut()) {
9104                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9105             }
9106             if (canShare()) {
9107                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9108                         ACCESSIBILITY_ACTION_SHARE,
9109                         getResources().getString(com.android.internal.R.string.share)));
9110             }
9111             if (canProcessText()) {  // also implies mEditor is not null.
9112                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9113             }
9114         }
9115 
9116         // Check for known input filter types.
9117         final int numFilters = mFilters.length;
9118         for (int i = 0; i < numFilters; i++) {
9119             final InputFilter filter = mFilters[i];
9120             if (filter instanceof InputFilter.LengthFilter) {
9121                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9122             }
9123         }
9124 
9125         if (!isSingleLine()) {
9126             info.setMultiLine(true);
9127         }
9128     }
9129 
9130     /**
9131      * Performs an accessibility action after it has been offered to the
9132      * delegate.
9133      *
9134      * @hide
9135      */
9136     @Override
performAccessibilityActionInternal(int action, Bundle arguments)9137     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
9138         if (mEditor != null
9139                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9140             return true;
9141         }
9142         switch (action) {
9143             case AccessibilityNodeInfo.ACTION_CLICK: {
9144                 return performAccessibilityActionClick(arguments);
9145             }
9146             case AccessibilityNodeInfo.ACTION_COPY: {
9147                 if (isFocused() && canCopy()) {
9148                     if (onTextContextMenuItem(ID_COPY)) {
9149                         return true;
9150                     }
9151                 }
9152             } return false;
9153             case AccessibilityNodeInfo.ACTION_PASTE: {
9154                 if (isFocused() && canPaste()) {
9155                     if (onTextContextMenuItem(ID_PASTE)) {
9156                         return true;
9157                     }
9158                 }
9159             } return false;
9160             case AccessibilityNodeInfo.ACTION_CUT: {
9161                 if (isFocused() && canCut()) {
9162                     if (onTextContextMenuItem(ID_CUT)) {
9163                         return true;
9164                     }
9165                 }
9166             } return false;
9167             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
9168                 ensureIterableTextForAccessibilitySelectable();
9169                 CharSequence text = getIterableTextForAccessibility();
9170                 if (text == null) {
9171                     return false;
9172                 }
9173                 final int start = (arguments != null) ? arguments.getInt(
9174                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9175                 final int end = (arguments != null) ? arguments.getInt(
9176                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9177                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9178                     // No arguments clears the selection.
9179                     if (start == end && end == -1) {
9180                         Selection.removeSelection((Spannable) text);
9181                         return true;
9182                     }
9183                     if (start >= 0 && start <= end && end <= text.length()) {
9184                         Selection.setSelection((Spannable) text, start, end);
9185                         // Make sure selection mode is engaged.
9186                         if (mEditor != null) {
9187                             mEditor.startSelectionActionMode();
9188                         }
9189                         return true;
9190                     }
9191                 }
9192             } return false;
9193             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9194             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9195                 ensureIterableTextForAccessibilitySelectable();
9196                 return super.performAccessibilityActionInternal(action, arguments);
9197             }
9198             case ACCESSIBILITY_ACTION_SHARE: {
9199                 if (isFocused() && canShare()) {
9200                     if (onTextContextMenuItem(ID_SHARE)) {
9201                         return true;
9202                     }
9203                 }
9204             } return false;
9205             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
9206                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
9207                     return false;
9208                 }
9209                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
9210                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
9211                 setText(text);
9212                 if (mText != null) {
9213                     int updatedTextLength = mText.length();
9214                     if (updatedTextLength > 0) {
9215                         Selection.setSelection((Spannable) mText, updatedTextLength);
9216                     }
9217                 }
9218             } return true;
9219             default: {
9220                 return super.performAccessibilityActionInternal(action, arguments);
9221             }
9222         }
9223     }
9224 
performAccessibilityActionClick(Bundle arguments)9225     private boolean performAccessibilityActionClick(Bundle arguments) {
9226         boolean handled = false;
9227 
9228         if (!isEnabled()) {
9229             return false;
9230         }
9231 
9232         if (isClickable() || isLongClickable()) {
9233             // Simulate View.onTouchEvent for an ACTION_UP event
9234             if (isFocusable() && !isFocused()) {
9235                 requestFocus();
9236             }
9237 
9238             performClick();
9239             handled = true;
9240         }
9241 
9242         // Show the IME, except when selecting in read-only text.
9243         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9244                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9245             final InputMethodManager imm = InputMethodManager.peekInstance();
9246             viewClicked(imm);
9247             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9248                 handled |= imm.showSoftInput(this, 0);
9249             }
9250         }
9251 
9252         return handled;
9253     }
9254 
hasSpannableText()9255     private boolean hasSpannableText() {
9256         return mText != null && mText instanceof Spannable;
9257     }
9258 
9259     /** @hide */
9260     @Override
sendAccessibilityEventInternal(int eventType)9261     public void sendAccessibilityEventInternal(int eventType) {
9262         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9263             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9264         }
9265 
9266         // Do not send scroll events since first they are not interesting for
9267         // accessibility and second such events a generated too frequently.
9268         // For details see the implementation of bringTextIntoView().
9269         if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9270             return;
9271         }
9272         super.sendAccessibilityEventInternal(eventType);
9273     }
9274 
9275     /**
9276      * Returns the text that should be exposed to accessibility services.
9277      * <p>
9278      * This approximates what is displayed visually. If the user has specified
9279      * that accessibility services should speak passwords, this method will
9280      * bypass any password transformation method and return unobscured text.
9281      *
9282      * @return the text that should be exposed to accessibility services, may
9283      *         be {@code null} if no text is set
9284      */
9285     @Nullable
getTextForAccessibility()9286     private CharSequence getTextForAccessibility() {
9287         // If the text is empty, we must be showing the hint text.
9288         if (TextUtils.isEmpty(mText)) {
9289             return mHint;
9290         }
9291 
9292         // Check whether we need to bypass the transformation
9293         // method and expose unobscured text.
9294         if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9295             return mText;
9296         }
9297 
9298         // Otherwise, speak whatever text is being displayed.
9299         return mTransformed;
9300     }
9301 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)9302     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9303             int fromIndex, int removedCount, int addedCount) {
9304         AccessibilityEvent event =
9305                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9306         event.setFromIndex(fromIndex);
9307         event.setRemovedCount(removedCount);
9308         event.setAddedCount(addedCount);
9309         event.setBeforeText(beforeText);
9310         sendAccessibilityEventUnchecked(event);
9311     }
9312 
9313     /**
9314      * Returns whether this text view is a current input method target.  The
9315      * default implementation just checks with {@link InputMethodManager}.
9316      */
isInputMethodTarget()9317     public boolean isInputMethodTarget() {
9318         InputMethodManager imm = InputMethodManager.peekInstance();
9319         return imm != null && imm.isActive(this);
9320     }
9321 
9322     static final int ID_SELECT_ALL = android.R.id.selectAll;
9323     static final int ID_UNDO = android.R.id.undo;
9324     static final int ID_REDO = android.R.id.redo;
9325     static final int ID_CUT = android.R.id.cut;
9326     static final int ID_COPY = android.R.id.copy;
9327     static final int ID_PASTE = android.R.id.paste;
9328     static final int ID_SHARE = android.R.id.shareText;
9329     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9330     static final int ID_REPLACE = android.R.id.replaceText;
9331 
9332     /**
9333      * Called when a context menu option for the text view is selected.  Currently
9334      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9335      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9336      *
9337      * @return true if the context menu item action was performed.
9338      */
onTextContextMenuItem(int id)9339     public boolean onTextContextMenuItem(int id) {
9340         int min = 0;
9341         int max = mText.length();
9342 
9343         if (isFocused()) {
9344             final int selStart = getSelectionStart();
9345             final int selEnd = getSelectionEnd();
9346 
9347             min = Math.max(0, Math.min(selStart, selEnd));
9348             max = Math.max(0, Math.max(selStart, selEnd));
9349         }
9350 
9351         switch (id) {
9352             case ID_SELECT_ALL:
9353                 selectAllText();
9354                 return true;
9355 
9356             case ID_UNDO:
9357                 if (mEditor != null) {
9358                     mEditor.undo();
9359                 }
9360                 return true;  // Returns true even if nothing was undone.
9361 
9362             case ID_REDO:
9363                 if (mEditor != null) {
9364                     mEditor.redo();
9365                 }
9366                 return true;  // Returns true even if nothing was undone.
9367 
9368             case ID_PASTE:
9369                 paste(min, max, true /* withFormatting */);
9370                 return true;
9371 
9372             case ID_PASTE_AS_PLAIN_TEXT:
9373                 paste(min, max, false /* withFormatting */);
9374                 return true;
9375 
9376             case ID_CUT:
9377                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9378                 deleteText_internal(min, max);
9379                 return true;
9380 
9381             case ID_COPY:
9382                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9383                 stopTextActionMode();
9384                 return true;
9385 
9386             case ID_REPLACE:
9387                 if (mEditor != null) {
9388                     mEditor.replace();
9389                 }
9390                 return true;
9391 
9392             case ID_SHARE:
9393                 shareSelectedText();
9394                 return true;
9395         }
9396         return false;
9397     }
9398 
getTransformedText(int start, int end)9399     CharSequence getTransformedText(int start, int end) {
9400         return removeSuggestionSpans(mTransformed.subSequence(start, end));
9401     }
9402 
9403     @Override
performLongClick()9404     public boolean performLongClick() {
9405         boolean handled = false;
9406 
9407         if (mEditor != null) {
9408             mEditor.mIsBeingLongClicked = true;
9409         }
9410 
9411         if (super.performLongClick()) {
9412             handled = true;
9413         }
9414 
9415         if (mEditor != null) {
9416             handled |= mEditor.performLongClick(handled);
9417             mEditor.mIsBeingLongClicked = false;
9418         }
9419 
9420         if (handled) {
9421             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9422             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9423         }
9424 
9425         return handled;
9426     }
9427 
9428     @Override
onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)9429     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9430         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9431         if (mEditor != null) {
9432             mEditor.onScrollChanged();
9433         }
9434     }
9435 
9436     /**
9437      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9438      * by the IME or by the spell checker as the user types. This is done by adding
9439      * {@link SuggestionSpan}s to the text.
9440      *
9441      * When suggestions are enabled (default), this list of suggestions will be displayed when the
9442      * user asks for them on these parts of the text. This value depends on the inputType of this
9443      * TextView.
9444      *
9445      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9446      *
9447      * In addition, the type variation must be one of
9448      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9449      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9450      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9451      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9452      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9453      *
9454      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9455      *
9456      * @return true if the suggestions popup window is enabled, based on the inputType.
9457      */
isSuggestionsEnabled()9458     public boolean isSuggestionsEnabled() {
9459         if (mEditor == null) return false;
9460         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9461             return false;
9462         }
9463         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9464 
9465         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9466         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9467                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9468                 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9469                 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9470                 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9471     }
9472 
9473     /**
9474      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9475      * selection is initiated in this View.
9476      *
9477      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9478      * Paste, Replace and Share actions, depending on what this View supports.
9479      *
9480      * <p>A custom implementation can add new entries in the default menu in its
9481      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9482      * method. The default actions can also be removed from the menu using
9483      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9484      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9485      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9486      *
9487      * <p>Returning false from
9488      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9489      * will prevent the action mode from being started.
9490      *
9491      * <p>Action click events should be handled by the custom implementation of
9492      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9493      * android.view.MenuItem)}.
9494      *
9495      * <p>Note that text selection mode is not started when a TextView receives focus and the
9496      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9497      * that case, to allow for quick replacement.
9498      */
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)9499     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9500         createEditorIfNeeded();
9501         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9502     }
9503 
9504     /**
9505      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9506      *
9507      * @return The current custom selection callback.
9508      */
getCustomSelectionActionModeCallback()9509     public ActionMode.Callback getCustomSelectionActionModeCallback() {
9510         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9511     }
9512 
9513     /**
9514      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9515      * insertion is initiated in this View.
9516      * The standard implementation populates the menu with a subset of Select All,
9517      * Paste and Replace actions, depending on what this View supports.
9518      *
9519      * <p>A custom implementation can add new entries in the default menu in its
9520      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9521      * android.view.Menu)} method. The default actions can also be removed from the menu using
9522      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9523      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9524      *
9525      * <p>Returning false from
9526      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9527      * android.view.Menu)} will prevent the action mode from being started.</p>
9528      *
9529      * <p>Action click events should be handled by the custom implementation of
9530      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9531      * android.view.MenuItem)}.</p>
9532      *
9533      * <p>Note that text insertion mode is not started when a TextView receives focus and the
9534      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9535      */
setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback)9536     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9537         createEditorIfNeeded();
9538         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9539     }
9540 
9541     /**
9542      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9543      *
9544      * @return The current custom insertion callback.
9545      */
getCustomInsertionActionModeCallback()9546     public ActionMode.Callback getCustomInsertionActionModeCallback() {
9547         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9548     }
9549 
9550     /**
9551      * @hide
9552      */
stopTextActionMode()9553     protected void stopTextActionMode() {
9554         if (mEditor != null) {
9555             mEditor.stopTextActionMode();
9556         }
9557     }
9558 
canUndo()9559     boolean canUndo() {
9560         return mEditor != null && mEditor.canUndo();
9561     }
9562 
canRedo()9563     boolean canRedo() {
9564         return mEditor != null && mEditor.canRedo();
9565     }
9566 
canCut()9567     boolean canCut() {
9568         if (hasPasswordTransformationMethod()) {
9569             return false;
9570         }
9571 
9572         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9573                 mEditor.mKeyListener != null) {
9574             return true;
9575         }
9576 
9577         return false;
9578     }
9579 
canCopy()9580     boolean canCopy() {
9581         if (hasPasswordTransformationMethod()) {
9582             return false;
9583         }
9584 
9585         if (mText.length() > 0 && hasSelection() && mEditor != null) {
9586             return true;
9587         }
9588 
9589         return false;
9590     }
9591 
canShare()9592     boolean canShare() {
9593         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
9594             return false;
9595         }
9596         return canCopy();
9597     }
9598 
isDeviceProvisioned()9599     boolean isDeviceProvisioned() {
9600         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
9601             mDeviceProvisionedState = Settings.Global.getInt(
9602                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
9603                     ? DEVICE_PROVISIONED_YES
9604                     : DEVICE_PROVISIONED_NO;
9605         }
9606         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
9607     }
9608 
canPaste()9609     boolean canPaste() {
9610         return (mText instanceof Editable &&
9611                 mEditor != null && mEditor.mKeyListener != null &&
9612                 getSelectionStart() >= 0 &&
9613                 getSelectionEnd() >= 0 &&
9614                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9615                 hasPrimaryClip());
9616     }
9617 
canProcessText()9618     boolean canProcessText() {
9619         if (getId() == View.NO_ID) {
9620             return false;
9621         }
9622         return canShare();
9623     }
9624 
canSelectAllText()9625     boolean canSelectAllText() {
9626         return canSelectText() && !hasPasswordTransformationMethod()
9627                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9628     }
9629 
selectAllText()9630     boolean selectAllText() {
9631         final int length = mText.length();
9632         Selection.setSelection((Spannable) mText, 0, length);
9633         return length > 0;
9634     }
9635 
replaceSelectionWithText(CharSequence text)9636     void replaceSelectionWithText(CharSequence text) {
9637         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9638     }
9639 
9640     /**
9641      * Paste clipboard content between min and max positions.
9642      */
paste(int min, int max, boolean withFormatting)9643     private void paste(int min, int max, boolean withFormatting) {
9644         ClipboardManager clipboard =
9645             (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9646         ClipData clip = clipboard.getPrimaryClip();
9647         if (clip != null) {
9648             boolean didFirst = false;
9649             for (int i=0; i<clip.getItemCount(); i++) {
9650                 final CharSequence paste;
9651                 if (withFormatting) {
9652                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
9653                 } else {
9654                     // Get an item as text and remove all spans by toString().
9655                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9656                     paste = (text instanceof Spanned) ? text.toString() : text;
9657                 }
9658                 if (paste != null) {
9659                     if (!didFirst) {
9660                         Selection.setSelection((Spannable) mText, max);
9661                         ((Editable) mText).replace(min, max, paste);
9662                         didFirst = true;
9663                     } else {
9664                         ((Editable) mText).insert(getSelectionEnd(), "\n");
9665                         ((Editable) mText).insert(getSelectionEnd(), paste);
9666                     }
9667                 }
9668             }
9669             sLastCutCopyOrTextChangedTime = 0;
9670         }
9671     }
9672 
shareSelectedText()9673     private void shareSelectedText() {
9674         String selectedText = getSelectedText();
9675         if (selectedText != null && !selectedText.isEmpty()) {
9676             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9677             sharingIntent.setType("text/plain");
9678             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9679             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9680             getContext().startActivity(Intent.createChooser(sharingIntent, null));
9681             Selection.setSelection((Spannable) mText, getSelectionEnd());
9682         }
9683     }
9684 
setPrimaryClip(ClipData clip)9685     private void setPrimaryClip(ClipData clip) {
9686         ClipboardManager clipboard = (ClipboardManager) getContext().
9687                 getSystemService(Context.CLIPBOARD_SERVICE);
9688         clipboard.setPrimaryClip(clip);
9689         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9690     }
9691 
9692     /**
9693      * Get the character offset closest to the specified absolute position. A typical use case is to
9694      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9695      *
9696      * @param x The horizontal absolute position of a point on screen
9697      * @param y The vertical absolute position of a point on screen
9698      * @return the character offset for the character whose position is closest to the specified
9699      *  position. Returns -1 if there is no layout.
9700      */
getOffsetForPosition(float x, float y)9701     public int getOffsetForPosition(float x, float y) {
9702         if (getLayout() == null) return -1;
9703         final int line = getLineAtCoordinate(y);
9704         final int offset = getOffsetAtCoordinate(line, x);
9705         return offset;
9706     }
9707 
convertToLocalHorizontalCoordinate(float x)9708     float convertToLocalHorizontalCoordinate(float x) {
9709         x -= getTotalPaddingLeft();
9710         // Clamp the position to inside of the view.
9711         x = Math.max(0.0f, x);
9712         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9713         x += getScrollX();
9714         return x;
9715     }
9716 
getLineAtCoordinate(float y)9717     int getLineAtCoordinate(float y) {
9718         y -= getTotalPaddingTop();
9719         // Clamp the position to inside of the view.
9720         y = Math.max(0.0f, y);
9721         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9722         y += getScrollY();
9723         return getLayout().getLineForVertical((int) y);
9724     }
9725 
getLineAtCoordinateUnclamped(float y)9726     int getLineAtCoordinateUnclamped(float y) {
9727         y -= getTotalPaddingTop();
9728         y += getScrollY();
9729         return getLayout().getLineForVertical((int) y);
9730     }
9731 
getOffsetAtCoordinate(int line, float x)9732     int getOffsetAtCoordinate(int line, float x) {
9733         x = convertToLocalHorizontalCoordinate(x);
9734         return getLayout().getOffsetForHorizontal(line, x);
9735     }
9736 
9737     @Override
onDragEvent(DragEvent event)9738     public boolean onDragEvent(DragEvent event) {
9739         switch (event.getAction()) {
9740             case DragEvent.ACTION_DRAG_STARTED:
9741                 return mEditor != null && mEditor.hasInsertionController();
9742 
9743             case DragEvent.ACTION_DRAG_ENTERED:
9744                 TextView.this.requestFocus();
9745                 return true;
9746 
9747             case DragEvent.ACTION_DRAG_LOCATION:
9748                 final int offset = getOffsetForPosition(event.getX(), event.getY());
9749                 Selection.setSelection((Spannable)mText, offset);
9750                 return true;
9751 
9752             case DragEvent.ACTION_DROP:
9753                 if (mEditor != null) mEditor.onDrop(event);
9754                 return true;
9755 
9756             case DragEvent.ACTION_DRAG_ENDED:
9757             case DragEvent.ACTION_DRAG_EXITED:
9758             default:
9759                 return true;
9760         }
9761     }
9762 
isInBatchEditMode()9763     boolean isInBatchEditMode() {
9764         if (mEditor == null) return false;
9765         final Editor.InputMethodState ims = mEditor.mInputMethodState;
9766         if (ims != null) {
9767             return ims.mBatchEditNesting > 0;
9768         }
9769         return mEditor.mInBatchEditControllers;
9770     }
9771 
9772     @Override
onRtlPropertiesChanged(int layoutDirection)9773     public void onRtlPropertiesChanged(int layoutDirection) {
9774         super.onRtlPropertiesChanged(layoutDirection);
9775 
9776         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9777         if (mTextDir != newTextDir) {
9778             mTextDir = newTextDir;
9779             if (mLayout != null) {
9780                 checkForRelayout();
9781             }
9782         }
9783     }
9784 
9785     /**
9786      * @hide
9787      */
getTextDirectionHeuristic()9788     protected TextDirectionHeuristic getTextDirectionHeuristic() {
9789         if (hasPasswordTransformationMethod()) {
9790             // passwords fields should be LTR
9791             return TextDirectionHeuristics.LTR;
9792         }
9793 
9794         // Always need to resolve layout direction first
9795         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9796 
9797         // Now, we can select the heuristic
9798         switch (getTextDirection()) {
9799             default:
9800             case TEXT_DIRECTION_FIRST_STRONG:
9801                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9802                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
9803             case TEXT_DIRECTION_ANY_RTL:
9804                 return TextDirectionHeuristics.ANYRTL_LTR;
9805             case TEXT_DIRECTION_LTR:
9806                 return TextDirectionHeuristics.LTR;
9807             case TEXT_DIRECTION_RTL:
9808                 return TextDirectionHeuristics.RTL;
9809             case TEXT_DIRECTION_LOCALE:
9810                 return TextDirectionHeuristics.LOCALE;
9811             case TEXT_DIRECTION_FIRST_STRONG_LTR:
9812                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9813             case TEXT_DIRECTION_FIRST_STRONG_RTL:
9814                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9815         }
9816     }
9817 
9818     /**
9819      * @hide
9820      */
9821     @Override
onResolveDrawables(int layoutDirection)9822     public void onResolveDrawables(int layoutDirection) {
9823         // No need to resolve twice
9824         if (mLastLayoutDirection == layoutDirection) {
9825             return;
9826         }
9827         mLastLayoutDirection = layoutDirection;
9828 
9829         // Resolve drawables
9830         if (mDrawables != null) {
9831             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
9832                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
9833                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
9834                 applyCompoundDrawableTint();
9835             }
9836         }
9837     }
9838 
9839     /**
9840      * Prepares a drawable for display by propagating layout direction and
9841      * drawable state.
9842      *
9843      * @param dr the drawable to prepare
9844      */
prepareDrawableForDisplay(@ullable Drawable dr)9845     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
9846         if (dr == null) {
9847             return;
9848         }
9849 
9850         dr.setLayoutDirection(getLayoutDirection());
9851 
9852         if (dr.isStateful()) {
9853             dr.setState(getDrawableState());
9854             dr.jumpToCurrentState();
9855         }
9856     }
9857 
9858     /**
9859      * @hide
9860      */
resetResolvedDrawables()9861     protected void resetResolvedDrawables() {
9862         super.resetResolvedDrawables();
9863         mLastLayoutDirection = -1;
9864     }
9865 
9866     /**
9867      * @hide
9868      */
viewClicked(InputMethodManager imm)9869     protected void viewClicked(InputMethodManager imm) {
9870         if (imm != null) {
9871             imm.viewClicked(this);
9872         }
9873     }
9874 
9875     /**
9876      * Deletes the range of text [start, end[.
9877      * @hide
9878      */
deleteText_internal(int start, int end)9879     protected void deleteText_internal(int start, int end) {
9880         ((Editable) mText).delete(start, end);
9881     }
9882 
9883     /**
9884      * Replaces the range of text [start, end[ by replacement text
9885      * @hide
9886      */
replaceText_internal(int start, int end, CharSequence text)9887     protected void replaceText_internal(int start, int end, CharSequence text) {
9888         ((Editable) mText).replace(start, end, text);
9889     }
9890 
9891     /**
9892      * Sets a span on the specified range of text
9893      * @hide
9894      */
setSpan_internal(Object span, int start, int end, int flags)9895     protected void setSpan_internal(Object span, int start, int end, int flags) {
9896         ((Editable) mText).setSpan(span, start, end, flags);
9897     }
9898 
9899     /**
9900      * Moves the cursor to the specified offset position in text
9901      * @hide
9902      */
setCursorPosition_internal(int start, int end)9903     protected void setCursorPosition_internal(int start, int end) {
9904         Selection.setSelection(((Editable) mText), start, end);
9905     }
9906 
9907     /**
9908      * An Editor should be created as soon as any of the editable-specific fields (grouped
9909      * inside the Editor object) is assigned to a non-default value.
9910      * This method will create the Editor if needed.
9911      *
9912      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9913      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9914      * Editor for backward compatibility, as soon as one of these fields is assigned.
9915      *
9916      * Also note that for performance reasons, the mEditor is created when needed, but not
9917      * reset when no more edit-specific fields are needed.
9918      */
createEditorIfNeeded()9919     private void createEditorIfNeeded() {
9920         if (mEditor == null) {
9921             mEditor = new Editor(this);
9922         }
9923     }
9924 
9925     /**
9926      * @hide
9927      */
9928     @Override
getIterableTextForAccessibility()9929     public CharSequence getIterableTextForAccessibility() {
9930         return mText;
9931     }
9932 
ensureIterableTextForAccessibilitySelectable()9933     private void ensureIterableTextForAccessibilitySelectable() {
9934         if (!(mText instanceof Spannable)) {
9935             setText(mText, BufferType.SPANNABLE);
9936         }
9937     }
9938 
9939     /**
9940      * @hide
9941      */
9942     @Override
getIteratorForGranularity(int granularity)9943     public TextSegmentIterator getIteratorForGranularity(int granularity) {
9944         switch (granularity) {
9945             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9946                 Spannable text = (Spannable) getIterableTextForAccessibility();
9947                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9948                     AccessibilityIterators.LineTextSegmentIterator iterator =
9949                         AccessibilityIterators.LineTextSegmentIterator.getInstance();
9950                     iterator.initialize(text, getLayout());
9951                     return iterator;
9952                 }
9953             } break;
9954             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9955                 Spannable text = (Spannable) getIterableTextForAccessibility();
9956                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9957                     AccessibilityIterators.PageTextSegmentIterator iterator =
9958                         AccessibilityIterators.PageTextSegmentIterator.getInstance();
9959                     iterator.initialize(this);
9960                     return iterator;
9961                 }
9962             } break;
9963         }
9964         return super.getIteratorForGranularity(granularity);
9965     }
9966 
9967     /**
9968      * @hide
9969      */
9970     @Override
getAccessibilitySelectionStart()9971     public int getAccessibilitySelectionStart() {
9972         return getSelectionStart();
9973     }
9974 
9975     /**
9976      * @hide
9977      */
isAccessibilitySelectionExtendable()9978     public boolean isAccessibilitySelectionExtendable() {
9979         return true;
9980     }
9981 
9982     /**
9983      * @hide
9984      */
9985     @Override
getAccessibilitySelectionEnd()9986     public int getAccessibilitySelectionEnd() {
9987         return getSelectionEnd();
9988     }
9989 
9990     /**
9991      * @hide
9992      */
9993     @Override
setAccessibilitySelection(int start, int end)9994     public void setAccessibilitySelection(int start, int end) {
9995         if (getAccessibilitySelectionStart() == start
9996                 && getAccessibilitySelectionEnd() == end) {
9997             return;
9998         }
9999         CharSequence text = getIterableTextForAccessibility();
10000         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
10001             Selection.setSelection((Spannable) text, start, end);
10002         } else {
10003             Selection.removeSelection((Spannable) text);
10004         }
10005         // Hide all selection controllers used for adjusting selection
10006         // since we are doing so explicitlty by other means and these
10007         // controllers interact with how selection behaves.
10008         if (mEditor != null) {
10009             mEditor.hideCursorAndSpanControllers();
10010             mEditor.stopTextActionMode();
10011         }
10012     }
10013 
10014     /** @hide */
10015     @Override
encodeProperties(@onNull ViewHierarchyEncoder stream)10016     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
10017         super.encodeProperties(stream);
10018 
10019         TruncateAt ellipsize = getEllipsize();
10020         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
10021         stream.addProperty("text:textSize", getTextSize());
10022         stream.addProperty("text:scaledTextSize", getScaledTextSize());
10023         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
10024         stream.addProperty("text:selectionStart", getSelectionStart());
10025         stream.addProperty("text:selectionEnd", getSelectionEnd());
10026         stream.addProperty("text:curTextColor", mCurTextColor);
10027         stream.addProperty("text:text", mText == null ? null : mText.toString());
10028         stream.addProperty("text:gravity", mGravity);
10029     }
10030 
10031     /**
10032      * User interface state that is stored by TextView for implementing
10033      * {@link View#onSaveInstanceState}.
10034      */
10035     public static class SavedState extends BaseSavedState {
10036         int selStart = -1;
10037         int selEnd = -1;
10038         CharSequence text;
10039         boolean frozenWithFocus;
10040         CharSequence error;
10041         ParcelableParcel editorState;  // Optional state from Editor.
10042 
SavedState(Parcelable superState)10043         SavedState(Parcelable superState) {
10044             super(superState);
10045         }
10046 
10047         @Override
writeToParcel(Parcel out, int flags)10048         public void writeToParcel(Parcel out, int flags) {
10049             super.writeToParcel(out, flags);
10050             out.writeInt(selStart);
10051             out.writeInt(selEnd);
10052             out.writeInt(frozenWithFocus ? 1 : 0);
10053             TextUtils.writeToParcel(text, out, flags);
10054 
10055             if (error == null) {
10056                 out.writeInt(0);
10057             } else {
10058                 out.writeInt(1);
10059                 TextUtils.writeToParcel(error, out, flags);
10060             }
10061 
10062             if (editorState == null) {
10063                 out.writeInt(0);
10064             } else {
10065                 out.writeInt(1);
10066                 editorState.writeToParcel(out, flags);
10067             }
10068         }
10069 
10070         @Override
toString()10071         public String toString() {
10072             String str = "TextView.SavedState{"
10073                     + Integer.toHexString(System.identityHashCode(this))
10074                     + " start=" + selStart + " end=" + selEnd;
10075             if (text != null) {
10076                 str += " text=" + text;
10077             }
10078             return str + "}";
10079         }
10080 
10081         @SuppressWarnings("hiding")
10082         public static final Parcelable.Creator<SavedState> CREATOR
10083                 = new Parcelable.Creator<SavedState>() {
10084             public SavedState createFromParcel(Parcel in) {
10085                 return new SavedState(in);
10086             }
10087 
10088             public SavedState[] newArray(int size) {
10089                 return new SavedState[size];
10090             }
10091         };
10092 
SavedState(Parcel in)10093         private SavedState(Parcel in) {
10094             super(in);
10095             selStart = in.readInt();
10096             selEnd = in.readInt();
10097             frozenWithFocus = (in.readInt() != 0);
10098             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10099 
10100             if (in.readInt() != 0) {
10101                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10102             }
10103 
10104             if (in.readInt() != 0) {
10105                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10106             }
10107         }
10108     }
10109 
10110     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10111         private char[] mChars;
10112         private int mStart, mLength;
10113 
CharWrapper(char[] chars, int start, int len)10114         public CharWrapper(char[] chars, int start, int len) {
10115             mChars = chars;
10116             mStart = start;
10117             mLength = len;
10118         }
10119 
set(char[] chars, int start, int len)10120         /* package */ void set(char[] chars, int start, int len) {
10121             mChars = chars;
10122             mStart = start;
10123             mLength = len;
10124         }
10125 
length()10126         public int length() {
10127             return mLength;
10128         }
10129 
charAt(int off)10130         public char charAt(int off) {
10131             return mChars[off + mStart];
10132         }
10133 
10134         @Override
toString()10135         public String toString() {
10136             return new String(mChars, mStart, mLength);
10137         }
10138 
subSequence(int start, int end)10139         public CharSequence subSequence(int start, int end) {
10140             if (start < 0 || end < 0 || start > mLength || end > mLength) {
10141                 throw new IndexOutOfBoundsException(start + ", " + end);
10142             }
10143 
10144             return new String(mChars, start + mStart, end - start);
10145         }
10146 
getChars(int start, int end, char[] buf, int off)10147         public void getChars(int start, int end, char[] buf, int off) {
10148             if (start < 0 || end < 0 || start > mLength || end > mLength) {
10149                 throw new IndexOutOfBoundsException(start + ", " + end);
10150             }
10151 
10152             System.arraycopy(mChars, start + mStart, buf, off, end - start);
10153         }
10154 
drawText(Canvas c, int start, int end, float x, float y, Paint p)10155         public void drawText(Canvas c, int start, int end,
10156                              float x, float y, Paint p) {
10157             c.drawText(mChars, start + mStart, end - start, x, y, p);
10158         }
10159 
drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)10160         public void drawTextRun(Canvas c, int start, int end,
10161                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
10162             int count = end - start;
10163             int contextCount = contextEnd - contextStart;
10164             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
10165                     contextCount, x, y, isRtl, p);
10166         }
10167 
measureText(int start, int end, Paint p)10168         public float measureText(int start, int end, Paint p) {
10169             return p.measureText(mChars, start + mStart, end - start);
10170         }
10171 
getTextWidths(int start, int end, float[] widths, Paint p)10172         public int getTextWidths(int start, int end, float[] widths, Paint p) {
10173             return p.getTextWidths(mChars, start + mStart, end - start, widths);
10174         }
10175 
getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)10176         public float getTextRunAdvances(int start, int end, int contextStart,
10177                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
10178                 Paint p) {
10179             int count = end - start;
10180             int contextCount = contextEnd - contextStart;
10181             return p.getTextRunAdvances(mChars, start + mStart, count,
10182                     contextStart + mStart, contextCount, isRtl, advances,
10183                     advancesIndex);
10184         }
10185 
getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)10186         public int getTextRunCursor(int contextStart, int contextEnd, int dir,
10187                 int offset, int cursorOpt, Paint p) {
10188             int contextCount = contextEnd - contextStart;
10189             return p.getTextRunCursor(mChars, contextStart + mStart,
10190                     contextCount, dir, offset + mStart, cursorOpt);
10191         }
10192     }
10193 
10194     private static final class Marquee {
10195         // TODO: Add an option to configure this
10196         private static final float MARQUEE_DELTA_MAX = 0.07f;
10197         private static final int MARQUEE_DELAY = 1200;
10198         private static final int MARQUEE_DP_PER_SECOND = 30;
10199 
10200         private static final byte MARQUEE_STOPPED = 0x0;
10201         private static final byte MARQUEE_STARTING = 0x1;
10202         private static final byte MARQUEE_RUNNING = 0x2;
10203 
10204         private final WeakReference<TextView> mView;
10205         private final Choreographer mChoreographer;
10206 
10207         private byte mStatus = MARQUEE_STOPPED;
10208         private final float mPixelsPerSecond;
10209         private float mMaxScroll;
10210         private float mMaxFadeScroll;
10211         private float mGhostStart;
10212         private float mGhostOffset;
10213         private float mFadeStop;
10214         private int mRepeatLimit;
10215 
10216         private float mScroll;
10217         private long mLastAnimationMs;
10218 
Marquee(TextView v)10219         Marquee(TextView v) {
10220             final float density = v.getContext().getResources().getDisplayMetrics().density;
10221             mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
10222             mView = new WeakReference<TextView>(v);
10223             mChoreographer = Choreographer.getInstance();
10224         }
10225 
10226         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10227             @Override
10228             public void doFrame(long frameTimeNanos) {
10229                 tick();
10230             }
10231         };
10232 
10233         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10234             @Override
10235             public void doFrame(long frameTimeNanos) {
10236                 mStatus = MARQUEE_RUNNING;
10237                 mLastAnimationMs = mChoreographer.getFrameTime();
10238                 tick();
10239             }
10240         };
10241 
10242         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10243             @Override
10244             public void doFrame(long frameTimeNanos) {
10245                 if (mStatus == MARQUEE_RUNNING) {
10246                     if (mRepeatLimit >= 0) {
10247                         mRepeatLimit--;
10248                     }
10249                     start(mRepeatLimit);
10250                 }
10251             }
10252         };
10253 
tick()10254         void tick() {
10255             if (mStatus != MARQUEE_RUNNING) {
10256                 return;
10257             }
10258 
10259             mChoreographer.removeFrameCallback(mTickCallback);
10260 
10261             final TextView textView = mView.get();
10262             if (textView != null && (textView.isFocused() || textView.isSelected())) {
10263                 long currentMs = mChoreographer.getFrameTime();
10264                 long deltaMs = currentMs - mLastAnimationMs;
10265                 mLastAnimationMs = currentMs;
10266                 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10267                 mScroll += deltaPx;
10268                 if (mScroll > mMaxScroll) {
10269                     mScroll = mMaxScroll;
10270                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
10271                 } else {
10272                     mChoreographer.postFrameCallback(mTickCallback);
10273                 }
10274                 textView.invalidate();
10275             }
10276         }
10277 
stop()10278         void stop() {
10279             mStatus = MARQUEE_STOPPED;
10280             mChoreographer.removeFrameCallback(mStartCallback);
10281             mChoreographer.removeFrameCallback(mRestartCallback);
10282             mChoreographer.removeFrameCallback(mTickCallback);
10283             resetScroll();
10284         }
10285 
resetScroll()10286         private void resetScroll() {
10287             mScroll = 0.0f;
10288             final TextView textView = mView.get();
10289             if (textView != null) textView.invalidate();
10290         }
10291 
start(int repeatLimit)10292         void start(int repeatLimit) {
10293             if (repeatLimit == 0) {
10294                 stop();
10295                 return;
10296             }
10297             mRepeatLimit = repeatLimit;
10298             final TextView textView = mView.get();
10299             if (textView != null && textView.mLayout != null) {
10300                 mStatus = MARQUEE_STARTING;
10301                 mScroll = 0.0f;
10302                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10303                         textView.getCompoundPaddingRight();
10304                 final float lineWidth = textView.mLayout.getLineWidth(0);
10305                 final float gap = textWidth / 3.0f;
10306                 mGhostStart = lineWidth - textWidth + gap;
10307                 mMaxScroll = mGhostStart + textWidth;
10308                 mGhostOffset = lineWidth + gap;
10309                 mFadeStop = lineWidth + textWidth / 6.0f;
10310                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10311 
10312                 textView.invalidate();
10313                 mChoreographer.postFrameCallback(mStartCallback);
10314             }
10315         }
10316 
getGhostOffset()10317         float getGhostOffset() {
10318             return mGhostOffset;
10319         }
10320 
getScroll()10321         float getScroll() {
10322             return mScroll;
10323         }
10324 
getMaxFadeScroll()10325         float getMaxFadeScroll() {
10326             return mMaxFadeScroll;
10327         }
10328 
shouldDrawLeftFade()10329         boolean shouldDrawLeftFade() {
10330             return mScroll <= mFadeStop;
10331         }
10332 
shouldDrawGhost()10333         boolean shouldDrawGhost() {
10334             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10335         }
10336 
isRunning()10337         boolean isRunning() {
10338             return mStatus == MARQUEE_RUNNING;
10339         }
10340 
isStopped()10341         boolean isStopped() {
10342             return mStatus == MARQUEE_STOPPED;
10343         }
10344     }
10345 
10346     private class ChangeWatcher implements TextWatcher, SpanWatcher {
10347 
10348         private CharSequence mBeforeText;
10349 
beforeTextChanged(CharSequence buffer, int start, int before, int after)10350         public void beforeTextChanged(CharSequence buffer, int start,
10351                                       int before, int after) {
10352             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10353                     + " before=" + before + " after=" + after + ": " + buffer);
10354 
10355             if (AccessibilityManager.getInstance(mContext).isEnabled()
10356                     && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10357                             || shouldSpeakPasswordsForAccessibility())) {
10358                 mBeforeText = buffer.toString();
10359             }
10360 
10361             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10362         }
10363 
onTextChanged(CharSequence buffer, int start, int before, int after)10364         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10365             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10366                     + " before=" + before + " after=" + after + ": " + buffer);
10367             TextView.this.handleTextChanged(buffer, start, before, after);
10368 
10369             if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10370                     (isFocused() || isSelected() && isShown())) {
10371                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10372                 mBeforeText = null;
10373             }
10374         }
10375 
afterTextChanged(Editable buffer)10376         public void afterTextChanged(Editable buffer) {
10377             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10378             TextView.this.sendAfterTextChanged(buffer);
10379 
10380             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10381                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10382             }
10383         }
10384 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)10385         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10386             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10387                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10388             TextView.this.spanChange(buf, what, s, st, e, en);
10389         }
10390 
onSpanAdded(Spannable buf, Object what, int s, int e)10391         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10392             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10393                     + " what=" + what + ": " + buf);
10394             TextView.this.spanChange(buf, what, -1, s, -1, e);
10395         }
10396 
onSpanRemoved(Spannable buf, Object what, int s, int e)10397         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10398             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10399                     + " what=" + what + ": " + buf);
10400             TextView.this.spanChange(buf, what, s, -1, e, -1);
10401         }
10402     }
10403 }
10404