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