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