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