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