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