OSDN Git Service

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