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