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