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