OSDN Git Service

Merge "Limit dotProduct value to 1.0f, so acosf would not return NaN." into mnc-dev
[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 (isInExtractedMode() && hasSelection() && mEditor != null
5234                 && mEditor.mTextActionMode == null) {
5235             mEditor.startSelectionActionMode();
5236         }
5237
5238         unregisterForPreDraw();
5239
5240         return true;
5241     }
5242
5243     @Override
5244     protected void onAttachedToWindow() {
5245         super.onAttachedToWindow();
5246
5247         mTemporaryDetach = false;
5248
5249         if (mEditor != null) mEditor.onAttachedToWindow();
5250
5251         if (mPreDrawListenerDetached) {
5252             getViewTreeObserver().addOnPreDrawListener(this);
5253             mPreDrawListenerDetached = false;
5254         }
5255     }
5256
5257     /** @hide */
5258     @Override
5259     protected void onDetachedFromWindowInternal() {
5260         if (mPreDrawRegistered) {
5261             getViewTreeObserver().removeOnPreDrawListener(this);
5262             mPreDrawListenerDetached = true;
5263         }
5264
5265         resetResolvedDrawables();
5266
5267         if (mEditor != null) mEditor.onDetachedFromWindow();
5268
5269         super.onDetachedFromWindowInternal();
5270     }
5271
5272     @Override
5273     public void onScreenStateChanged(int screenState) {
5274         super.onScreenStateChanged(screenState);
5275         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5276     }
5277
5278     @Override
5279     protected boolean isPaddingOffsetRequired() {
5280         return mShadowRadius != 0 || mDrawables != null;
5281     }
5282
5283     @Override
5284     protected int getLeftPaddingOffset() {
5285         return getCompoundPaddingLeft() - mPaddingLeft +
5286                 (int) Math.min(0, mShadowDx - mShadowRadius);
5287     }
5288
5289     @Override
5290     protected int getTopPaddingOffset() {
5291         return (int) Math.min(0, mShadowDy - mShadowRadius);
5292     }
5293
5294     @Override
5295     protected int getBottomPaddingOffset() {
5296         return (int) Math.max(0, mShadowDy + mShadowRadius);
5297     }
5298
5299     private int getFudgedPaddingRight() {
5300         // Add sufficient space for cursor and tone marks
5301         int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5302         return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
5303     }
5304
5305     @Override
5306     protected int getRightPaddingOffset() {
5307         return -(getFudgedPaddingRight() - mPaddingRight) +
5308                 (int) Math.max(0, mShadowDx + mShadowRadius);
5309     }
5310
5311     @Override
5312     protected boolean verifyDrawable(Drawable who) {
5313         final boolean verified = super.verifyDrawable(who);
5314         if (!verified && mDrawables != null) {
5315             for (Drawable dr : mDrawables.mShowing) {
5316                 if (who == dr) {
5317                     return true;
5318                 }
5319             }
5320         }
5321         return verified;
5322     }
5323
5324     @Override
5325     public void jumpDrawablesToCurrentState() {
5326         super.jumpDrawablesToCurrentState();
5327         if (mDrawables != null) {
5328             for (Drawable dr : mDrawables.mShowing) {
5329                 if (dr != null) {
5330                     dr.jumpToCurrentState();
5331                 }
5332             }
5333         }
5334     }
5335
5336     @Override
5337     public void invalidateDrawable(Drawable drawable) {
5338         boolean handled = false;
5339
5340         if (verifyDrawable(drawable)) {
5341             final Rect dirty = drawable.getBounds();
5342             int scrollX = mScrollX;
5343             int scrollY = mScrollY;
5344
5345             // IMPORTANT: The coordinates below are based on the coordinates computed
5346             // for each compound drawable in onDraw(). Make sure to update each section
5347             // accordingly.
5348             final TextView.Drawables drawables = mDrawables;
5349             if (drawables != null) {
5350                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5351                     final int compoundPaddingTop = getCompoundPaddingTop();
5352                     final int compoundPaddingBottom = getCompoundPaddingBottom();
5353                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5354
5355                     scrollX += mPaddingLeft;
5356                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5357                     handled = true;
5358                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5359                     final int compoundPaddingTop = getCompoundPaddingTop();
5360                     final int compoundPaddingBottom = getCompoundPaddingBottom();
5361                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5362
5363                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5364                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5365                     handled = true;
5366                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5367                     final int compoundPaddingLeft = getCompoundPaddingLeft();
5368                     final int compoundPaddingRight = getCompoundPaddingRight();
5369                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5370
5371                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5372                     scrollY += mPaddingTop;
5373                     handled = true;
5374                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5375                     final int compoundPaddingLeft = getCompoundPaddingLeft();
5376                     final int compoundPaddingRight = getCompoundPaddingRight();
5377                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5378
5379                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5380                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5381                     handled = true;
5382                 }
5383             }
5384
5385             if (handled) {
5386                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5387                         dirty.right + scrollX, dirty.bottom + scrollY);
5388             }
5389         }
5390
5391         if (!handled) {
5392             super.invalidateDrawable(drawable);
5393         }
5394     }
5395
5396     @Override
5397     public boolean hasOverlappingRendering() {
5398         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5399         return ((getBackground() != null && getBackground().getCurrent() != null)
5400                 || mText instanceof Spannable || hasSelection()
5401                 || isHorizontalFadingEdgeEnabled());
5402     }
5403
5404     /**
5405      *
5406      * Returns the state of the {@code textIsSelectable} flag (See
5407      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5408      * to allow users to select and copy text in a non-editable TextView, the content of an
5409      * {@link EditText} can always be selected, independently of the value of this flag.
5410      * <p>
5411      *
5412      * @return True if the text displayed in this TextView can be selected by the user.
5413      *
5414      * @attr ref android.R.styleable#TextView_textIsSelectable
5415      */
5416     public boolean isTextSelectable() {
5417         return mEditor == null ? false : mEditor.mTextIsSelectable;
5418     }
5419
5420     /**
5421      * Sets whether the content of this view is selectable by the user. The default is
5422      * {@code false}, meaning that the content is not selectable.
5423      * <p>
5424      * When you use a TextView to display a useful piece of information to the user (such as a
5425      * contact's address), make it selectable, so that the user can select and copy its
5426      * content. You can also use set the XML attribute
5427      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5428      * <p>
5429      * When you call this method to set the value of {@code textIsSelectable}, it sets
5430      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5431      * and {@code longClickable} to the same value. These flags correspond to the attributes
5432      * {@link android.R.styleable#View_focusable android:focusable},
5433      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5434      * {@link android.R.styleable#View_clickable android:clickable}, and
5435      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5436      * flags to a state you had set previously, call one or more of the following methods:
5437      * {@link #setFocusable(boolean) setFocusable()},
5438      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5439      * {@link #setClickable(boolean) setClickable()} or
5440      * {@link #setLongClickable(boolean) setLongClickable()}.
5441      *
5442      * @param selectable Whether the content of this TextView should be selectable.
5443      */
5444     public void setTextIsSelectable(boolean selectable) {
5445         if (!selectable && mEditor == null) return; // false is default value with no edit data
5446
5447         createEditorIfNeeded();
5448         if (mEditor.mTextIsSelectable == selectable) return;
5449
5450         mEditor.mTextIsSelectable = selectable;
5451         setFocusableInTouchMode(selectable);
5452         setFocusable(selectable);
5453         setClickable(selectable);
5454         setLongClickable(selectable);
5455
5456         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5457
5458         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5459         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5460
5461         // Called by setText above, but safer in case of future code changes
5462         mEditor.prepareCursorControllers();
5463     }
5464
5465     @Override
5466     protected int[] onCreateDrawableState(int extraSpace) {
5467         final int[] drawableState;
5468
5469         if (mSingleLine) {
5470             drawableState = super.onCreateDrawableState(extraSpace);
5471         } else {
5472             drawableState = super.onCreateDrawableState(extraSpace + 1);
5473             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5474         }
5475
5476         if (isTextSelectable()) {
5477             // Disable pressed state, which was introduced when TextView was made clickable.
5478             // Prevents text color change.
5479             // setClickable(false) would have a similar effect, but it also disables focus changes
5480             // and long press actions, which are both needed by text selection.
5481             final int length = drawableState.length;
5482             for (int i = 0; i < length; i++) {
5483                 if (drawableState[i] == R.attr.state_pressed) {
5484                     final int[] nonPressedState = new int[length - 1];
5485                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5486                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5487                     return nonPressedState;
5488                 }
5489             }
5490         }
5491
5492         return drawableState;
5493     }
5494
5495     private Path getUpdatedHighlightPath() {
5496         Path highlight = null;
5497         Paint highlightPaint = mHighlightPaint;
5498
5499         final int selStart = getSelectionStart();
5500         final int selEnd = getSelectionEnd();
5501         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5502             if (selStart == selEnd) {
5503                 if (mEditor != null && mEditor.isCursorVisible() &&
5504                         (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5505                         (2 * Editor.BLINK) < Editor.BLINK) {
5506                     if (mHighlightPathBogus) {
5507                         if (mHighlightPath == null) mHighlightPath = new Path();
5508                         mHighlightPath.reset();
5509                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
5510                         mEditor.updateCursorsPositions();
5511                         mHighlightPathBogus = false;
5512                     }
5513
5514                     // XXX should pass to skin instead of drawing directly
5515                     highlightPaint.setColor(mCurTextColor);
5516                     highlightPaint.setStyle(Paint.Style.STROKE);
5517                     highlight = mHighlightPath;
5518                 }
5519             } else {
5520                 if (mHighlightPathBogus) {
5521                     if (mHighlightPath == null) mHighlightPath = new Path();
5522                     mHighlightPath.reset();
5523                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5524                     mHighlightPathBogus = false;
5525                 }
5526
5527                 // XXX should pass to skin instead of drawing directly
5528                 highlightPaint.setColor(mHighlightColor);
5529                 highlightPaint.setStyle(Paint.Style.FILL);
5530
5531                 highlight = mHighlightPath;
5532             }
5533         }
5534         return highlight;
5535     }
5536
5537     /**
5538      * @hide
5539      */
5540     public int getHorizontalOffsetForDrawables() {
5541         return 0;
5542     }
5543
5544     @Override
5545     protected void onDraw(Canvas canvas) {
5546         restartMarqueeIfNeeded();
5547
5548         // Draw the background for this view
5549         super.onDraw(canvas);
5550
5551         final int compoundPaddingLeft = getCompoundPaddingLeft();
5552         final int compoundPaddingTop = getCompoundPaddingTop();
5553         final int compoundPaddingRight = getCompoundPaddingRight();
5554         final int compoundPaddingBottom = getCompoundPaddingBottom();
5555         final int scrollX = mScrollX;
5556         final int scrollY = mScrollY;
5557         final int right = mRight;
5558         final int left = mLeft;
5559         final int bottom = mBottom;
5560         final int top = mTop;
5561         final boolean isLayoutRtl = isLayoutRtl();
5562         final int offset = getHorizontalOffsetForDrawables();
5563         final int leftOffset = isLayoutRtl ? 0 : offset;
5564         final int rightOffset = isLayoutRtl ? offset : 0 ;
5565
5566         final Drawables dr = mDrawables;
5567         if (dr != null) {
5568             /*
5569              * Compound, not extended, because the icon is not clipped
5570              * if the text height is smaller.
5571              */
5572
5573             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5574             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5575
5576             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5577             // Make sure to update invalidateDrawable() when changing this code.
5578             if (dr.mShowing[Drawables.LEFT] != null) {
5579                 canvas.save();
5580                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5581                                  scrollY + compoundPaddingTop +
5582                                  (vspace - dr.mDrawableHeightLeft) / 2);
5583                 dr.mShowing[Drawables.LEFT].draw(canvas);
5584                 canvas.restore();
5585             }
5586
5587             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5588             // Make sure to update invalidateDrawable() when changing this code.
5589             if (dr.mShowing[Drawables.RIGHT] != null) {
5590                 canvas.save();
5591                 canvas.translate(scrollX + right - left - mPaddingRight
5592                         - dr.mDrawableSizeRight - rightOffset,
5593                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5594                 dr.mShowing[Drawables.RIGHT].draw(canvas);
5595                 canvas.restore();
5596             }
5597
5598             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5599             // Make sure to update invalidateDrawable() when changing this code.
5600             if (dr.mShowing[Drawables.TOP] != null) {
5601                 canvas.save();
5602                 canvas.translate(scrollX + compoundPaddingLeft +
5603                         (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5604                 dr.mShowing[Drawables.TOP].draw(canvas);
5605                 canvas.restore();
5606             }
5607
5608             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5609             // Make sure to update invalidateDrawable() when changing this code.
5610             if (dr.mShowing[Drawables.BOTTOM] != null) {
5611                 canvas.save();
5612                 canvas.translate(scrollX + compoundPaddingLeft +
5613                         (hspace - dr.mDrawableWidthBottom) / 2,
5614                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5615                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5616                 canvas.restore();
5617             }
5618         }
5619
5620         int color = mCurTextColor;
5621
5622         if (mLayout == null) {
5623             assumeLayout();
5624         }
5625
5626         Layout layout = mLayout;
5627
5628         if (mHint != null && mText.length() == 0) {
5629             if (mHintTextColor != null) {
5630                 color = mCurHintTextColor;
5631             }
5632
5633             layout = mHintLayout;
5634         }
5635
5636         mTextPaint.setColor(color);
5637         mTextPaint.drawableState = getDrawableState();
5638
5639         canvas.save();
5640         /*  Would be faster if we didn't have to do this. Can we chop the
5641             (displayable) text so that we don't need to do this ever?
5642         */
5643
5644         int extendedPaddingTop = getExtendedPaddingTop();
5645         int extendedPaddingBottom = getExtendedPaddingBottom();
5646
5647         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5648         final int maxScrollY = mLayout.getHeight() - vspace;
5649
5650         float clipLeft = compoundPaddingLeft + scrollX;
5651         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5652         float clipRight = right - left - getFudgedPaddingRight() + scrollX;
5653         float clipBottom = bottom - top + scrollY -
5654                 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5655
5656         if (mShadowRadius != 0) {
5657             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5658             clipRight += Math.max(0, mShadowDx + mShadowRadius);
5659
5660             clipTop += Math.min(0, mShadowDy - mShadowRadius);
5661             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5662         }
5663
5664         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5665
5666         int voffsetText = 0;
5667         int voffsetCursor = 0;
5668
5669         // translate in by our padding
5670         /* shortcircuit calling getVerticaOffset() */
5671         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5672             voffsetText = getVerticalOffset(false);
5673             voffsetCursor = getVerticalOffset(true);
5674         }
5675         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5676
5677         final int layoutDirection = getLayoutDirection();
5678         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5679         if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5680                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5681             if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5682                     (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5683                 final int width = mRight - mLeft;
5684                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5685                 final float dx = mLayout.getLineRight(0) - (width - padding);
5686                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5687             }
5688
5689             if (mMarquee != null && mMarquee.isRunning()) {
5690                 final float dx = -mMarquee.getScroll();
5691                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5692             }
5693         }
5694
5695         final int cursorOffsetVertical = voffsetCursor - voffsetText;
5696
5697         Path highlight = getUpdatedHighlightPath();
5698         if (mEditor != null) {
5699             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5700         } else {
5701             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5702         }
5703
5704         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5705             final float dx = mMarquee.getGhostOffset();
5706             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5707             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5708         }
5709
5710         canvas.restore();
5711     }
5712
5713     @Override
5714     public void getFocusedRect(Rect r) {
5715         if (mLayout == null) {
5716             super.getFocusedRect(r);
5717             return;
5718         }
5719
5720         int selEnd = getSelectionEnd();
5721         if (selEnd < 0) {
5722             super.getFocusedRect(r);
5723             return;
5724         }
5725
5726         int selStart = getSelectionStart();
5727         if (selStart < 0 || selStart >= selEnd) {
5728             int line = mLayout.getLineForOffset(selEnd);
5729             r.top = mLayout.getLineTop(line);
5730             r.bottom = mLayout.getLineBottom(line);
5731             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5732             r.right = r.left + 4;
5733         } else {
5734             int lineStart = mLayout.getLineForOffset(selStart);
5735             int lineEnd = mLayout.getLineForOffset(selEnd);
5736             r.top = mLayout.getLineTop(lineStart);
5737             r.bottom = mLayout.getLineBottom(lineEnd);
5738             if (lineStart == lineEnd) {
5739                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5740                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5741             } else {
5742                 // Selection extends across multiple lines -- make the focused
5743                 // rect cover the entire width.
5744                 if (mHighlightPathBogus) {
5745                     if (mHighlightPath == null) mHighlightPath = new Path();
5746                     mHighlightPath.reset();
5747                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5748                     mHighlightPathBogus = false;
5749                 }
5750                 synchronized (TEMP_RECTF) {
5751                     mHighlightPath.computeBounds(TEMP_RECTF, true);
5752                     r.left = (int)TEMP_RECTF.left-1;
5753                     r.right = (int)TEMP_RECTF.right+1;
5754                 }
5755             }
5756         }
5757
5758         // Adjust for padding and gravity.
5759         int paddingLeft = getCompoundPaddingLeft();
5760         int paddingTop = getExtendedPaddingTop();
5761         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5762             paddingTop += getVerticalOffset(false);
5763         }
5764         r.offset(paddingLeft, paddingTop);
5765         int paddingBottom = getExtendedPaddingBottom();
5766         r.bottom += paddingBottom;
5767     }
5768
5769     /**
5770      * Return the number of lines of text, or 0 if the internal Layout has not
5771      * been built.
5772      */
5773     public int getLineCount() {
5774         return mLayout != null ? mLayout.getLineCount() : 0;
5775     }
5776
5777     /**
5778      * Return the baseline for the specified line (0...getLineCount() - 1)
5779      * If bounds is not null, return the top, left, right, bottom extents
5780      * of the specified line in it. If the internal Layout has not been built,
5781      * return 0 and set bounds to (0, 0, 0, 0)
5782      * @param line which line to examine (0..getLineCount() - 1)
5783      * @param bounds Optional. If not null, it returns the extent of the line
5784      * @return the Y-coordinate of the baseline
5785      */
5786     public int getLineBounds(int line, Rect bounds) {
5787         if (mLayout == null) {
5788             if (bounds != null) {
5789                 bounds.set(0, 0, 0, 0);
5790             }
5791             return 0;
5792         }
5793         else {
5794             int baseline = mLayout.getLineBounds(line, bounds);
5795
5796             int voffset = getExtendedPaddingTop();
5797             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5798                 voffset += getVerticalOffset(true);
5799             }
5800             if (bounds != null) {
5801                 bounds.offset(getCompoundPaddingLeft(), voffset);
5802             }
5803             return baseline + voffset;
5804         }
5805     }
5806
5807     @Override
5808     public int getBaseline() {
5809         if (mLayout == null) {
5810             return super.getBaseline();
5811         }
5812
5813         int voffset = 0;
5814         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5815             voffset = getVerticalOffset(true);
5816         }
5817
5818         if (isLayoutModeOptical(mParent)) {
5819             voffset -= getOpticalInsets().top;
5820         }
5821
5822         return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5823     }
5824
5825     /**
5826      * @hide
5827      */
5828     @Override
5829     protected int getFadeTop(boolean offsetRequired) {
5830         if (mLayout == null) return 0;
5831
5832         int voffset = 0;
5833         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5834             voffset = getVerticalOffset(true);
5835         }
5836
5837         if (offsetRequired) voffset += getTopPaddingOffset();
5838
5839         return getExtendedPaddingTop() + voffset;
5840     }
5841
5842     /**
5843      * @hide
5844      */
5845     @Override
5846     protected int getFadeHeight(boolean offsetRequired) {
5847         return mLayout != null ? mLayout.getHeight() : 0;
5848     }
5849
5850     @Override
5851     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5852         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
5853         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
5854         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
5855         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
5856             return true;
5857         }
5858         return super.onKeyPreIme(keyCode, event);
5859     }
5860
5861     /**
5862      * @hide
5863      */
5864     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
5865         // Do nothing unless mEditor is in text action mode.
5866         if (mEditor == null || mEditor.mTextActionMode == null) {
5867             return false;
5868         }
5869
5870         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5871             KeyEvent.DispatcherState state = getKeyDispatcherState();
5872             if (state != null) {
5873                 state.startTracking(event, this);
5874             }
5875             return true;
5876         } else if (event.getAction() == KeyEvent.ACTION_UP) {
5877             KeyEvent.DispatcherState state = getKeyDispatcherState();
5878             if (state != null) {
5879                 state.handleUpEvent(event);
5880             }
5881             if (event.isTracking() && !event.isCanceled()) {
5882                 stopTextActionMode();
5883                 return true;
5884             }
5885         }
5886         return false;
5887     }
5888
5889     @Override
5890     public boolean onKeyDown(int keyCode, KeyEvent event) {
5891         int which = doKeyDown(keyCode, event, null);
5892         if (which == 0) {
5893             return super.onKeyDown(keyCode, event);
5894         }
5895
5896         return true;
5897     }
5898
5899     @Override
5900     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5901         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5902
5903         int which = doKeyDown(keyCode, down, event);
5904         if (which == 0) {
5905             // Go through default dispatching.
5906             return super.onKeyMultiple(keyCode, repeatCount, event);
5907         }
5908         if (which == -1) {
5909             // Consumed the whole thing.
5910             return true;
5911         }
5912
5913         repeatCount--;
5914
5915         // We are going to dispatch the remaining events to either the input
5916         // or movement method.  To do this, we will just send a repeated stream
5917         // of down and up events until we have done the complete repeatCount.
5918         // It would be nice if those interfaces had an onKeyMultiple() method,
5919         // but adding that is a more complicated change.
5920         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5921         if (which == 1) {
5922             // mEditor and mEditor.mInput are not null from doKeyDown
5923             mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5924             while (--repeatCount > 0) {
5925                 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5926                 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5927             }
5928             hideErrorIfUnchanged();
5929
5930         } else if (which == 2) {
5931             // mMovement is not null from doKeyDown
5932             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5933             while (--repeatCount > 0) {
5934                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5935                 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5936             }
5937         }
5938
5939         return true;
5940     }
5941
5942     /**
5943      * Returns true if pressing ENTER in this field advances focus instead
5944      * of inserting the character.  This is true mostly in single-line fields,
5945      * but also in mail addresses and subjects which will display on multiple
5946      * lines but where it doesn't make sense to insert newlines.
5947      */
5948     private boolean shouldAdvanceFocusOnEnter() {
5949         if (getKeyListener() == null) {
5950             return false;
5951         }
5952
5953         if (mSingleLine) {
5954             return true;
5955         }
5956
5957         if (mEditor != null &&
5958                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5959             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5960             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5961                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5962                 return true;
5963             }
5964         }
5965
5966         return false;
5967     }
5968
5969     /**
5970      * Returns true if pressing TAB in this field advances focus instead
5971      * of inserting the character.  Insert tabs only in multi-line editors.
5972      */
5973     private boolean shouldAdvanceFocusOnTab() {
5974         if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5975                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5976             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5977             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5978                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5979                 return false;
5980             }
5981         }
5982         return true;
5983     }
5984
5985     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5986         if (!isEnabled()) {
5987             return 0;
5988         }
5989
5990         // If this is the initial keydown, we don't want to prevent a movement away from this view.
5991         // While this shouldn't be necessary because any time we're preventing default movement we
5992         // should be restricting the focus to remain within this view, thus we'll also receive
5993         // the key up event, occasionally key up events will get dropped and we don't want to
5994         // prevent the user from traversing out of this on the next key down.
5995         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5996             mPreventDefaultMovement = false;
5997         }
5998
5999         switch (keyCode) {
6000             case KeyEvent.KEYCODE_ENTER:
6001                 if (event.hasNoModifiers()) {
6002                     // When mInputContentType is set, we know that we are
6003                     // running in a "modern" cupcake environment, so don't need
6004                     // to worry about the application trying to capture
6005                     // enter key events.
6006                     if (mEditor != null && mEditor.mInputContentType != null) {
6007                         // If there is an action listener, given them a
6008                         // chance to consume the event.
6009                         if (mEditor.mInputContentType.onEditorActionListener != null &&
6010                                 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6011                                 this, EditorInfo.IME_NULL, event)) {
6012                             mEditor.mInputContentType.enterDown = true;
6013                             // We are consuming the enter key for them.
6014                             return -1;
6015                         }
6016                     }
6017
6018                     // If our editor should move focus when enter is pressed, or
6019                     // this is a generated event from an IME action button, then
6020                     // don't let it be inserted into the text.
6021                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6022                             || shouldAdvanceFocusOnEnter()) {
6023                         if (hasOnClickListeners()) {
6024                             return 0;
6025                         }
6026                         return -1;
6027                     }
6028                 }
6029                 break;
6030
6031             case KeyEvent.KEYCODE_DPAD_CENTER:
6032                 if (event.hasNoModifiers()) {
6033                     if (shouldAdvanceFocusOnEnter()) {
6034                         return 0;
6035                     }
6036                 }
6037                 break;
6038
6039             case KeyEvent.KEYCODE_TAB:
6040                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6041                     if (shouldAdvanceFocusOnTab()) {
6042                         return 0;
6043                     }
6044                 }
6045                 break;
6046
6047                 // Has to be done on key down (and not on key up) to correctly be intercepted.
6048             case KeyEvent.KEYCODE_BACK:
6049                 if (mEditor != null && mEditor.mTextActionMode != null) {
6050                     stopTextActionMode();
6051                     return -1;
6052                 }
6053                 break;
6054         }
6055
6056         if (mEditor != null && mEditor.mKeyListener != null) {
6057             boolean doDown = true;
6058             if (otherEvent != null) {
6059                 try {
6060                     beginBatchEdit();
6061                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6062                             otherEvent);
6063                     hideErrorIfUnchanged();
6064                     doDown = false;
6065                     if (handled) {
6066                         return -1;
6067                     }
6068                 } catch (AbstractMethodError e) {
6069                     // onKeyOther was added after 1.0, so if it isn't
6070                     // implemented we need to try to dispatch as a regular down.
6071                 } finally {
6072                     endBatchEdit();
6073                 }
6074             }
6075
6076             if (doDown) {
6077                 beginBatchEdit();
6078                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6079                         keyCode, event);
6080                 endBatchEdit();
6081                 hideErrorIfUnchanged();
6082                 if (handled) return 1;
6083             }
6084         }
6085
6086         // bug 650865: sometimes we get a key event before a layout.
6087         // don't try to move around if we don't know the layout.
6088
6089         if (mMovement != null && mLayout != null) {
6090             boolean doDown = true;
6091             if (otherEvent != null) {
6092                 try {
6093                     boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6094                             otherEvent);
6095                     doDown = false;
6096                     if (handled) {
6097                         return -1;
6098                     }
6099                 } catch (AbstractMethodError e) {
6100                     // onKeyOther was added after 1.0, so if it isn't
6101                     // implemented we need to try to dispatch as a regular down.
6102                 }
6103             }
6104             if (doDown) {
6105                 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6106                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6107                         mPreventDefaultMovement = true;
6108                     }
6109                     return 2;
6110                 }
6111             }
6112         }
6113
6114         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
6115     }
6116
6117     /**
6118      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6119      * can be recorded.
6120      * @hide
6121      */
6122     public void resetErrorChangedFlag() {
6123         /*
6124          * Keep track of what the error was before doing the input
6125          * so that if an input filter changed the error, we leave
6126          * that error showing.  Otherwise, we take down whatever
6127          * error was showing when the user types something.
6128          */
6129         if (mEditor != null) mEditor.mErrorWasChanged = false;
6130     }
6131
6132     /**
6133      * @hide
6134      */
6135     public void hideErrorIfUnchanged() {
6136         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6137             setError(null, null);
6138         }
6139     }
6140
6141     @Override
6142     public boolean onKeyUp(int keyCode, KeyEvent event) {
6143         if (!isEnabled()) {
6144             return super.onKeyUp(keyCode, event);
6145         }
6146
6147         if (!KeyEvent.isModifierKey(keyCode)) {
6148             mPreventDefaultMovement = false;
6149         }
6150
6151         switch (keyCode) {
6152             case KeyEvent.KEYCODE_DPAD_CENTER:
6153                 if (event.hasNoModifiers()) {
6154                     /*
6155                      * If there is a click listener, just call through to
6156                      * super, which will invoke it.
6157                      *
6158                      * If there isn't a click listener, try to show the soft
6159                      * input method.  (It will also
6160                      * call performClick(), but that won't do anything in
6161                      * this case.)
6162                      */
6163                     if (!hasOnClickListeners()) {
6164                         if (mMovement != null && mText instanceof Editable
6165                                 && mLayout != null && onCheckIsTextEditor()) {
6166                             InputMethodManager imm = InputMethodManager.peekInstance();
6167                             viewClicked(imm);
6168                             if (imm != null && getShowSoftInputOnFocus()) {
6169                                 imm.showSoftInput(this, 0);
6170                             }
6171                         }
6172                     }
6173                 }
6174                 return super.onKeyUp(keyCode, event);
6175
6176             case KeyEvent.KEYCODE_ENTER:
6177                 if (event.hasNoModifiers()) {
6178                     if (mEditor != null && mEditor.mInputContentType != null
6179                             && mEditor.mInputContentType.onEditorActionListener != null
6180                             && mEditor.mInputContentType.enterDown) {
6181                         mEditor.mInputContentType.enterDown = false;
6182                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6183                                 this, EditorInfo.IME_NULL, event)) {
6184                             return true;
6185                         }
6186                     }
6187
6188                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6189                             || shouldAdvanceFocusOnEnter()) {
6190                         /*
6191                          * If there is a click listener, just call through to
6192                          * super, which will invoke it.
6193                          *
6194                          * If there isn't a click listener, try to advance focus,
6195                          * but still call through to super, which will reset the
6196                          * pressed state and longpress state.  (It will also
6197                          * call performClick(), but that won't do anything in
6198                          * this case.)
6199                          */
6200                         if (!hasOnClickListeners()) {
6201                             View v = focusSearch(FOCUS_DOWN);
6202
6203                             if (v != null) {
6204                                 if (!v.requestFocus(FOCUS_DOWN)) {
6205                                     throw new IllegalStateException(
6206                                             "focus search returned a view " +
6207                                             "that wasn't able to take focus!");
6208                                 }
6209
6210                                 /*
6211                                  * Return true because we handled the key; super
6212                                  * will return false because there was no click
6213                                  * listener.
6214                                  */
6215                                 super.onKeyUp(keyCode, event);
6216                                 return true;
6217                             } else if ((event.getFlags()
6218                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6219                                 // No target for next focus, but make sure the IME
6220                                 // if this came from it.
6221                                 InputMethodManager imm = InputMethodManager.peekInstance();
6222                                 if (imm != null && imm.isActive(this)) {
6223                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
6224                                 }
6225                             }
6226                         }
6227                     }
6228                     return super.onKeyUp(keyCode, event);
6229                 }
6230                 break;
6231         }
6232
6233         if (mEditor != null && mEditor.mKeyListener != null)
6234             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6235                 return true;
6236
6237         if (mMovement != null && mLayout != null)
6238             if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6239                 return true;
6240
6241         return super.onKeyUp(keyCode, event);
6242     }
6243
6244     @Override
6245     public boolean onCheckIsTextEditor() {
6246         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6247     }
6248
6249     @Override
6250     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6251         if (onCheckIsTextEditor() && isEnabled()) {
6252             mEditor.createInputMethodStateIfNeeded();
6253             outAttrs.inputType = getInputType();
6254             if (mEditor.mInputContentType != null) {
6255                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6256                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6257                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6258                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6259                 outAttrs.extras = mEditor.mInputContentType.extras;
6260             } else {
6261                 outAttrs.imeOptions = EditorInfo.IME_NULL;
6262             }
6263             if (focusSearch(FOCUS_DOWN) != null) {
6264                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6265             }
6266             if (focusSearch(FOCUS_UP) != null) {
6267                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6268             }
6269             if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6270                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
6271                 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6272                     // An action has not been set, but the enter key will move to
6273                     // the next focus, so set the action to that.
6274                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6275                 } else {
6276                     // An action has not been set, and there is no focus to move
6277                     // to, so let's just supply a "done" action.
6278                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6279                 }
6280                 if (!shouldAdvanceFocusOnEnter()) {
6281                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6282                 }
6283             }
6284             if (isMultilineInputType(outAttrs.inputType)) {
6285                 // Multi-line text editors should always show an enter key.
6286                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6287             }
6288             outAttrs.hintText = mHint;
6289             if (mText instanceof Editable) {
6290                 InputConnection ic = new EditableInputConnection(this);
6291                 outAttrs.initialSelStart = getSelectionStart();
6292                 outAttrs.initialSelEnd = getSelectionEnd();
6293                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6294                 return ic;
6295             }
6296         }
6297         return null;
6298     }
6299
6300     /**
6301      * If this TextView contains editable content, extract a portion of it
6302      * based on the information in <var>request</var> in to <var>outText</var>.
6303      * @return Returns true if the text was successfully extracted, else false.
6304      */
6305     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6306         createEditorIfNeeded();
6307         return mEditor.extractText(request, outText);
6308     }
6309
6310     /**
6311      * This is used to remove all style-impacting spans from text before new
6312      * extracted text is being replaced into it, so that we don't have any
6313      * lingering spans applied during the replace.
6314      */
6315     static void removeParcelableSpans(Spannable spannable, int start, int end) {
6316         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6317         int i = spans.length;
6318         while (i > 0) {
6319             i--;
6320             spannable.removeSpan(spans[i]);
6321         }
6322     }
6323
6324     /**
6325      * Apply to this text view the given extracted text, as previously
6326      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6327      */
6328     public void setExtractedText(ExtractedText text) {
6329         Editable content = getEditableText();
6330         if (text.text != null) {
6331             if (content == null) {
6332                 setText(text.text, TextView.BufferType.EDITABLE);
6333             } else if (text.partialStartOffset < 0) {
6334                 removeParcelableSpans(content, 0, content.length());
6335                 content.replace(0, content.length(), text.text);
6336             } else {
6337                 final int N = content.length();
6338                 int start = text.partialStartOffset;
6339                 if (start > N) start = N;
6340                 int end = text.partialEndOffset;
6341                 if (end > N) end = N;
6342                 removeParcelableSpans(content, start, end);
6343                 content.replace(start, end, text.text);
6344             }
6345         }
6346
6347         // Now set the selection position...  make sure it is in range, to
6348         // avoid crashes.  If this is a partial update, it is possible that
6349         // the underlying text may have changed, causing us problems here.
6350         // Also we just don't want to trust clients to do the right thing.
6351         Spannable sp = (Spannable)getText();
6352         final int N = sp.length();
6353         int start = text.selectionStart;
6354         if (start < 0) start = 0;
6355         else if (start > N) start = N;
6356         int end = text.selectionEnd;
6357         if (end < 0) end = 0;
6358         else if (end > N) end = N;
6359         Selection.setSelection(sp, start, end);
6360
6361         // Finally, update the selection mode.
6362         if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6363             MetaKeyKeyListener.startSelecting(this, sp);
6364         } else {
6365             MetaKeyKeyListener.stopSelecting(this, sp);
6366         }
6367     }
6368
6369     /**
6370      * @hide
6371      */
6372     public void setExtracting(ExtractedTextRequest req) {
6373         if (mEditor.mInputMethodState != null) {
6374             mEditor.mInputMethodState.mExtractedTextRequest = req;
6375         }
6376         // This would stop a possible selection mode, but no such mode is started in case
6377         // extracted mode will start. Some text is selected though, and will trigger an action mode
6378         // in the extracted view.
6379         mEditor.hideCursorAndSpanControllers();
6380         stopTextActionMode();
6381     }
6382
6383     /**
6384      * Called by the framework in response to a text completion from
6385      * the current input method, provided by it calling
6386      * {@link InputConnection#commitCompletion
6387      * InputConnection.commitCompletion()}.  The default implementation does
6388      * nothing; text views that are supporting auto-completion should override
6389      * this to do their desired behavior.
6390      *
6391      * @param text The auto complete text the user has selected.
6392      */
6393     public void onCommitCompletion(CompletionInfo text) {
6394         // intentionally empty
6395     }
6396
6397     /**
6398      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6399      * a dictionnary) from the current input method, provided by it calling
6400      * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6401      * implementation flashes the background of the corrected word to provide feedback to the user.
6402      *
6403      * @param info The auto correct info about the text that was corrected.
6404      */
6405     public void onCommitCorrection(CorrectionInfo info) {
6406         if (mEditor != null) mEditor.onCommitCorrection(info);
6407     }
6408
6409     public void beginBatchEdit() {
6410         if (mEditor != null) mEditor.beginBatchEdit();
6411     }
6412
6413     public void endBatchEdit() {
6414         if (mEditor != null) mEditor.endBatchEdit();
6415     }
6416
6417     /**
6418      * Called by the framework in response to a request to begin a batch
6419      * of edit operations through a call to link {@link #beginBatchEdit()}.
6420      */
6421     public void onBeginBatchEdit() {
6422         // intentionally empty
6423     }
6424
6425     /**
6426      * Called by the framework in response to a request to end a batch
6427      * of edit operations through a call to link {@link #endBatchEdit}.
6428      */
6429     public void onEndBatchEdit() {
6430         // intentionally empty
6431     }
6432
6433     /**
6434      * Called by the framework in response to a private command from the
6435      * current method, provided by it calling
6436      * {@link InputConnection#performPrivateCommand
6437      * InputConnection.performPrivateCommand()}.
6438      *
6439      * @param action The action name of the command.
6440      * @param data Any additional data for the command.  This may be null.
6441      * @return Return true if you handled the command, else false.
6442      */
6443     public boolean onPrivateIMECommand(String action, Bundle data) {
6444         return false;
6445     }
6446
6447     private void nullLayouts() {
6448         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6449             mSavedLayout = (BoringLayout) mLayout;
6450         }
6451         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6452             mSavedHintLayout = (BoringLayout) mHintLayout;
6453         }
6454
6455         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6456
6457         mBoring = mHintBoring = null;
6458
6459         // Since it depends on the value of mLayout
6460         if (mEditor != null) mEditor.prepareCursorControllers();
6461     }
6462
6463     /**
6464      * Make a new Layout based on the already-measured size of the view,
6465      * on the assumption that it was measured correctly at some point.
6466      */
6467     private void assumeLayout() {
6468         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6469
6470         if (width < 1) {
6471             width = 0;
6472         }
6473
6474         int physicalWidth = width;
6475
6476         if (mHorizontallyScrolling) {
6477             width = VERY_WIDE;
6478         }
6479
6480         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6481                       physicalWidth, false);
6482     }
6483
6484     private Layout.Alignment getLayoutAlignment() {
6485         Layout.Alignment alignment;
6486         switch (getTextAlignment()) {
6487             case TEXT_ALIGNMENT_GRAVITY:
6488                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6489                     case Gravity.START:
6490                         alignment = Layout.Alignment.ALIGN_NORMAL;
6491                         break;
6492                     case Gravity.END:
6493                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
6494                         break;
6495                     case Gravity.LEFT:
6496                         alignment = Layout.Alignment.ALIGN_LEFT;
6497                         break;
6498                     case Gravity.RIGHT:
6499                         alignment = Layout.Alignment.ALIGN_RIGHT;
6500                         break;
6501                     case Gravity.CENTER_HORIZONTAL:
6502                         alignment = Layout.Alignment.ALIGN_CENTER;
6503                         break;
6504                     default:
6505                         alignment = Layout.Alignment.ALIGN_NORMAL;
6506                         break;
6507                 }
6508                 break;
6509             case TEXT_ALIGNMENT_TEXT_START:
6510                 alignment = Layout.Alignment.ALIGN_NORMAL;
6511                 break;
6512             case TEXT_ALIGNMENT_TEXT_END:
6513                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6514                 break;
6515             case TEXT_ALIGNMENT_CENTER:
6516                 alignment = Layout.Alignment.ALIGN_CENTER;
6517                 break;
6518             case TEXT_ALIGNMENT_VIEW_START:
6519                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6520                         Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6521                 break;
6522             case TEXT_ALIGNMENT_VIEW_END:
6523                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6524                         Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6525                 break;
6526             case TEXT_ALIGNMENT_INHERIT:
6527                 // This should never happen as we have already resolved the text alignment
6528                 // but better safe than sorry so we just fall through
6529             default:
6530                 alignment = Layout.Alignment.ALIGN_NORMAL;
6531                 break;
6532         }
6533         return alignment;
6534     }
6535
6536     /**
6537      * The width passed in is now the desired layout width,
6538      * not the full view width with padding.
6539      * {@hide}
6540      */
6541     protected void makeNewLayout(int wantWidth, int hintWidth,
6542                                  BoringLayout.Metrics boring,
6543                                  BoringLayout.Metrics hintBoring,
6544                                  int ellipsisWidth, boolean bringIntoView) {
6545         stopMarquee();
6546
6547         // Update "old" cached values
6548         mOldMaximum = mMaximum;
6549         mOldMaxMode = mMaxMode;
6550
6551         mHighlightPathBogus = true;
6552
6553         if (wantWidth < 0) {
6554             wantWidth = 0;
6555         }
6556         if (hintWidth < 0) {
6557             hintWidth = 0;
6558         }
6559
6560         Layout.Alignment alignment = getLayoutAlignment();
6561         final boolean testDirChange = mSingleLine && mLayout != null &&
6562             (alignment == Layout.Alignment.ALIGN_NORMAL ||
6563              alignment == Layout.Alignment.ALIGN_OPPOSITE);
6564         int oldDir = 0;
6565         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6566         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6567         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6568                 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6569         TruncateAt effectiveEllipsize = mEllipsize;
6570         if (mEllipsize == TruncateAt.MARQUEE &&
6571                 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6572             effectiveEllipsize = TruncateAt.END_SMALL;
6573         }
6574
6575         if (mTextDir == null) {
6576             mTextDir = getTextDirectionHeuristic();
6577         }
6578
6579         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6580                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6581         if (switchEllipsize) {
6582             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6583                     TruncateAt.END : TruncateAt.MARQUEE;
6584             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6585                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6586         }
6587
6588         shouldEllipsize = mEllipsize != null;
6589         mHintLayout = null;
6590
6591         if (mHint != null) {
6592             if (shouldEllipsize) hintWidth = wantWidth;
6593
6594             if (hintBoring == UNKNOWN_BORING) {
6595                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6596                                                    mHintBoring);
6597                 if (hintBoring != null) {
6598                     mHintBoring = hintBoring;
6599                 }
6600             }
6601
6602             if (hintBoring != null) {
6603                 if (hintBoring.width <= hintWidth &&
6604                     (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6605                     if (mSavedHintLayout != null) {
6606                         mHintLayout = mSavedHintLayout.
6607                                 replaceOrMake(mHint, mTextPaint,
6608                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6609                                 hintBoring, mIncludePad);
6610                     } else {
6611                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
6612                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6613                                 hintBoring, mIncludePad);
6614                     }
6615
6616                     mSavedHintLayout = (BoringLayout) mHintLayout;
6617                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6618                     if (mSavedHintLayout != null) {
6619                         mHintLayout = mSavedHintLayout.
6620                                 replaceOrMake(mHint, mTextPaint,
6621                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6622                                 hintBoring, mIncludePad, mEllipsize,
6623                                 ellipsisWidth);
6624                     } else {
6625                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
6626                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6627                                 hintBoring, mIncludePad, mEllipsize,
6628                                 ellipsisWidth);
6629                     }
6630                 }
6631             }
6632             // TODO: code duplication with makeSingleLayout()
6633             if (mHintLayout == null) {
6634                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6635                         mHint.length(), mTextPaint, hintWidth)
6636                         .setAlignment(alignment)
6637                         .setTextDirection(mTextDir)
6638                         .setLineSpacing(mSpacingAdd, mSpacingMult)
6639                         .setIncludePad(mIncludePad)
6640                         .setBreakStrategy(mBreakStrategy)
6641                         .setHyphenationFrequency(mHyphenationFrequency);
6642                 if (shouldEllipsize) {
6643                     builder.setEllipsize(mEllipsize)
6644                             .setEllipsizedWidth(ellipsisWidth)
6645                             .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6646                 }
6647                 mHintLayout = builder.build();
6648             }
6649         }
6650
6651         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6652             registerForPreDraw();
6653         }
6654
6655         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6656             if (!compressText(ellipsisWidth)) {
6657                 final int height = mLayoutParams.height;
6658                 // If the size of the view does not depend on the size of the text, try to
6659                 // start the marquee immediately
6660                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6661                     startMarquee();
6662                 } else {
6663                     // Defer the start of the marquee until we know our width (see setFrame())
6664                     mRestartMarquee = true;
6665                 }
6666             }
6667         }
6668
6669         // CursorControllers need a non-null mLayout
6670         if (mEditor != null) mEditor.prepareCursorControllers();
6671     }
6672
6673     private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6674             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6675             boolean useSaved) {
6676         Layout result = null;
6677         if (mText instanceof Spannable) {
6678             result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6679                     alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6680                     mBreakStrategy, mHyphenationFrequency,
6681                     getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6682         } else {
6683             if (boring == UNKNOWN_BORING) {
6684                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6685                 if (boring != null) {
6686                     mBoring = boring;
6687                 }
6688             }
6689
6690             if (boring != null) {
6691                 if (boring.width <= wantWidth &&
6692                         (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6693                     if (useSaved && mSavedLayout != null) {
6694                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6695                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6696                                 boring, mIncludePad);
6697                     } else {
6698                         result = BoringLayout.make(mTransformed, mTextPaint,
6699                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6700                                 boring, mIncludePad);
6701                     }
6702
6703                     if (useSaved) {
6704                         mSavedLayout = (BoringLayout) result;
6705                     }
6706                 } else if (shouldEllipsize && boring.width <= wantWidth) {
6707                     if (useSaved && mSavedLayout != null) {
6708                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6709                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6710                                 boring, mIncludePad, effectiveEllipsize,
6711                                 ellipsisWidth);
6712                     } else {
6713                         result = BoringLayout.make(mTransformed, mTextPaint,
6714                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6715                                 boring, mIncludePad, effectiveEllipsize,
6716                                 ellipsisWidth);
6717                     }
6718                 }
6719             }
6720         }
6721         if (result == null) {
6722             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6723                     0, mTransformed.length(), mTextPaint, wantWidth)
6724                     .setAlignment(alignment)
6725                     .setTextDirection(mTextDir)
6726                     .setLineSpacing(mSpacingAdd, mSpacingMult)
6727                     .setIncludePad(mIncludePad)
6728                     .setBreakStrategy(mBreakStrategy)
6729                     .setHyphenationFrequency(mHyphenationFrequency);
6730             if (shouldEllipsize) {
6731                 builder.setEllipsize(effectiveEllipsize)
6732                         .setEllipsizedWidth(ellipsisWidth)
6733                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6734             }
6735             // TODO: explore always setting maxLines
6736             result = builder.build();
6737         }
6738         return result;
6739     }
6740
6741     private boolean compressText(float width) {
6742         if (isHardwareAccelerated()) return false;
6743
6744         // Only compress the text if it hasn't been compressed by the previous pass
6745         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6746                 mTextPaint.getTextScaleX() == 1.0f) {
6747             final float textWidth = mLayout.getLineWidth(0);
6748             final float overflow = (textWidth + 1.0f - width) / width;
6749             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6750                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6751                 post(new Runnable() {
6752                     public void run() {
6753                         requestLayout();
6754                     }
6755                 });
6756                 return true;
6757             }
6758         }
6759
6760         return false;
6761     }
6762
6763     private static int desired(Layout layout) {
6764         int n = layout.getLineCount();
6765         CharSequence text = layout.getText();
6766         float max = 0;
6767
6768         // if any line was wrapped, we can't use it.
6769         // but it's ok for the last line not to have a newline
6770
6771         for (int i = 0; i < n - 1; i++) {
6772             if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6773                 return -1;
6774         }
6775
6776         for (int i = 0; i < n; i++) {
6777             max = Math.max(max, layout.getLineWidth(i));
6778         }
6779
6780         return (int) Math.ceil(max);
6781     }
6782
6783     /**
6784      * Set whether the TextView includes extra top and bottom padding to make
6785      * room for accents that go above the normal ascent and descent.
6786      * The default is true.
6787      *
6788      * @see #getIncludeFontPadding()
6789      *
6790      * @attr ref android.R.styleable#TextView_includeFontPadding
6791      */
6792     public void setIncludeFontPadding(boolean includepad) {
6793         if (mIncludePad != includepad) {
6794             mIncludePad = includepad;
6795
6796             if (mLayout != null) {
6797                 nullLayouts();
6798                 requestLayout();
6799                 invalidate();
6800             }
6801         }
6802     }
6803
6804     /**
6805      * Gets whether the TextView includes extra top and bottom padding to make
6806      * room for accents that go above the normal ascent and descent.
6807      *
6808      * @see #setIncludeFontPadding(boolean)
6809      *
6810      * @attr ref android.R.styleable#TextView_includeFontPadding
6811      */
6812     public boolean getIncludeFontPadding() {
6813         return mIncludePad;
6814     }
6815
6816     private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6817
6818     @Override
6819     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6820         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6821         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6822         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6823         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6824
6825         int width;
6826         int height;
6827
6828         BoringLayout.Metrics boring = UNKNOWN_BORING;
6829         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6830
6831         if (mTextDir == null) {
6832             mTextDir = getTextDirectionHeuristic();
6833         }
6834
6835         int des = -1;
6836         boolean fromexisting = false;
6837
6838         if (widthMode == MeasureSpec.EXACTLY) {
6839             // Parent has told us how big to be. So be it.
6840             width = widthSize;
6841         } else {
6842             if (mLayout != null && mEllipsize == null) {
6843                 des = desired(mLayout);
6844             }
6845
6846             if (des < 0) {
6847                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6848                 if (boring != null) {
6849                     mBoring = boring;
6850                 }
6851             } else {
6852                 fromexisting = true;
6853             }
6854
6855             if (boring == null || boring == UNKNOWN_BORING) {
6856                 if (des < 0) {
6857                     des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6858                 }
6859                 width = des;
6860             } else {
6861                 width = boring.width;
6862             }
6863
6864             final Drawables dr = mDrawables;
6865             if (dr != null) {
6866                 width = Math.max(width, dr.mDrawableWidthTop);
6867                 width = Math.max(width, dr.mDrawableWidthBottom);
6868             }
6869
6870             if (mHint != null) {
6871                 int hintDes = -1;
6872                 int hintWidth;
6873
6874                 if (mHintLayout != null && mEllipsize == null) {
6875                     hintDes = desired(mHintLayout);
6876                 }
6877
6878                 if (hintDes < 0) {
6879                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
6880                     if (hintBoring != null) {
6881                         mHintBoring = hintBoring;
6882                     }
6883                 }
6884
6885                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6886                     if (hintDes < 0) {
6887                         hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
6888                     }
6889                     hintWidth = hintDes;
6890                 } else {
6891                     hintWidth = hintBoring.width;
6892                 }
6893
6894                 if (hintWidth > width) {
6895                     width = hintWidth;
6896                 }
6897             }
6898
6899             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6900
6901             if (mMaxWidthMode == EMS) {
6902                 width = Math.min(width, mMaxWidth * getLineHeight());
6903             } else {
6904                 width = Math.min(width, mMaxWidth);
6905             }
6906
6907             if (mMinWidthMode == EMS) {
6908                 width = Math.max(width, mMinWidth * getLineHeight());
6909             } else {
6910                 width = Math.max(width, mMinWidth);
6911             }
6912
6913             // Check against our minimum width
6914             width = Math.max(width, getSuggestedMinimumWidth());
6915
6916             if (widthMode == MeasureSpec.AT_MOST) {
6917                 width = Math.min(widthSize, width);
6918             }
6919         }
6920
6921         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6922         int unpaddedWidth = want;
6923
6924         if (mHorizontallyScrolling) want = VERY_WIDE;
6925
6926         int hintWant = want;
6927         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6928
6929         if (mLayout == null) {
6930             makeNewLayout(want, hintWant, boring, hintBoring,
6931                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6932         } else {
6933             final boolean layoutChanged = (mLayout.getWidth() != want) ||
6934                     (hintWidth != hintWant) ||
6935                     (mLayout.getEllipsizedWidth() !=
6936                             width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6937
6938             final boolean widthChanged = (mHint == null) &&
6939                     (mEllipsize == null) &&
6940                     (want > mLayout.getWidth()) &&
6941                     (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6942
6943             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6944
6945             if (layoutChanged || maximumChanged) {
6946                 if (!maximumChanged && widthChanged) {
6947                     mLayout.increaseWidthTo(want);
6948                 } else {
6949                     makeNewLayout(want, hintWant, boring, hintBoring,
6950                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6951                 }
6952             } else {
6953                 // Nothing has changed
6954             }
6955         }
6956
6957         if (heightMode == MeasureSpec.EXACTLY) {
6958             // Parent has told us how big to be. So be it.
6959             height = heightSize;
6960             mDesiredHeightAtMeasure = -1;
6961         } else {
6962             int desired = getDesiredHeight();
6963
6964             height = desired;
6965             mDesiredHeightAtMeasure = desired;
6966
6967             if (heightMode == MeasureSpec.AT_MOST) {
6968                 height = Math.min(desired, heightSize);
6969             }
6970         }
6971
6972         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6973         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6974             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6975         }
6976
6977         /*
6978          * We didn't let makeNewLayout() register to bring the cursor into view,
6979          * so do it here if there is any possibility that it is needed.
6980          */
6981         if (mMovement != null ||
6982             mLayout.getWidth() > unpaddedWidth ||
6983             mLayout.getHeight() > unpaddedHeight) {
6984             registerForPreDraw();
6985         } else {
6986             scrollTo(0, 0);
6987         }
6988
6989         setMeasuredDimension(width, height);
6990     }
6991
6992     private int getDesiredHeight() {
6993         return Math.max(
6994                 getDesiredHeight(mLayout, true),
6995                 getDesiredHeight(mHintLayout, mEllipsize != null));
6996     }
6997
6998     private int getDesiredHeight(Layout layout, boolean cap) {
6999         if (layout == null) {
7000             return 0;
7001         }
7002
7003         int linecount = layout.getLineCount();
7004         int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7005         int desired = layout.getLineTop(linecount);
7006
7007         final Drawables dr = mDrawables;
7008         if (dr != null) {
7009             desired = Math.max(desired, dr.mDrawableHeightLeft);
7010             desired = Math.max(desired, dr.mDrawableHeightRight);
7011         }
7012
7013         desired += pad;
7014
7015         if (mMaxMode == LINES) {
7016             /*
7017              * Don't cap the hint to a certain number of lines.
7018              * (Do cap it, though, if we have a maximum pixel height.)
7019              */
7020             if (cap) {
7021                 if (linecount > mMaximum) {
7022                     desired = layout.getLineTop(mMaximum);
7023
7024                     if (dr != null) {
7025                         desired = Math.max(desired, dr.mDrawableHeightLeft);
7026                         desired = Math.max(desired, dr.mDrawableHeightRight);
7027                     }
7028
7029                     desired += pad;
7030                     linecount = mMaximum;
7031                 }
7032             }
7033         } else {
7034             desired = Math.min(desired, mMaximum);
7035         }
7036
7037         if (mMinMode == LINES) {
7038             if (linecount < mMinimum) {
7039                 desired += getLineHeight() * (mMinimum - linecount);
7040             }
7041         } else {
7042             desired = Math.max(desired, mMinimum);
7043         }
7044
7045         // Check against our minimum height
7046         desired = Math.max(desired, getSuggestedMinimumHeight());
7047
7048         return desired;
7049     }
7050
7051     /**
7052      * Check whether a change to the existing text layout requires a
7053      * new view layout.
7054      */
7055     private void checkForResize() {
7056         boolean sizeChanged = false;
7057
7058         if (mLayout != null) {
7059             // Check if our width changed
7060             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7061                 sizeChanged = true;
7062                 invalidate();
7063             }
7064
7065             // Check if our height changed
7066             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7067                 int desiredHeight = getDesiredHeight();
7068
7069                 if (desiredHeight != this.getHeight()) {
7070                     sizeChanged = true;
7071                 }
7072             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7073                 if (mDesiredHeightAtMeasure >= 0) {
7074                     int desiredHeight = getDesiredHeight();
7075
7076                     if (desiredHeight != mDesiredHeightAtMeasure) {
7077                         sizeChanged = true;
7078                     }
7079                 }
7080             }
7081         }
7082
7083         if (sizeChanged) {
7084             requestLayout();
7085             // caller will have already invalidated
7086         }
7087     }
7088
7089     /**
7090      * Check whether entirely new text requires a new view layout
7091      * or merely a new text layout.
7092      */
7093     private void checkForRelayout() {
7094         // If we have a fixed width, we can just swap in a new text layout
7095         // if the text height stays the same or if the view height is fixed.
7096
7097         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7098                 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7099                 (mHint == null || mHintLayout != null) &&
7100                 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7101             // Static width, so try making a new text layout.
7102
7103             int oldht = mLayout.getHeight();
7104             int want = mLayout.getWidth();
7105             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7106
7107             /*
7108              * No need to bring the text into view, since the size is not
7109              * changing (unless we do the requestLayout(), in which case it
7110              * will happen at measure).
7111              */
7112             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7113                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7114                           false);
7115
7116             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7117                 // In a fixed-height view, so use our new text layout.
7118                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7119                     mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7120                     invalidate();
7121                     return;
7122                 }
7123
7124                 // Dynamic height, but height has stayed the same,
7125                 // so use our new text layout.
7126                 if (mLayout.getHeight() == oldht &&
7127                     (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7128                     invalidate();
7129                     return;
7130                 }
7131             }
7132
7133             // We lose: the height has changed and we have a dynamic height.
7134             // Request a new view layout using our new text layout.
7135             requestLayout();
7136             invalidate();
7137         } else {
7138             // Dynamic width, so we have no choice but to request a new
7139             // view layout with a new text layout.
7140             nullLayouts();
7141             requestLayout();
7142             invalidate();
7143         }
7144     }
7145
7146     @Override
7147     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7148         super.onLayout(changed, left, top, right, bottom);
7149         if (mDeferScroll >= 0) {
7150             int curs = mDeferScroll;
7151             mDeferScroll = -1;
7152             bringPointIntoView(Math.min(curs, mText.length()));
7153         }
7154     }
7155
7156     private boolean isShowingHint() {
7157         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7158     }
7159
7160     /**
7161      * Returns true if anything changed.
7162      */
7163     private boolean bringTextIntoView() {
7164         Layout layout = isShowingHint() ? mHintLayout : mLayout;
7165         int line = 0;
7166         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7167             line = layout.getLineCount() - 1;
7168         }
7169
7170         Layout.Alignment a = layout.getParagraphAlignment(line);
7171         int dir = layout.getParagraphDirection(line);
7172         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7173         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7174         int ht = layout.getHeight();
7175
7176         int scrollx, scrolly;
7177
7178         // Convert to left, center, or right alignment.
7179         if (a == Layout.Alignment.ALIGN_NORMAL) {
7180             a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7181                 Layout.Alignment.ALIGN_RIGHT;
7182         } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7183             a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7184                 Layout.Alignment.ALIGN_LEFT;
7185         }
7186
7187         if (a == Layout.Alignment.ALIGN_CENTER) {
7188             /*
7189              * Keep centered if possible, or, if it is too wide to fit,
7190              * keep leading edge in view.
7191              */
7192
7193             int left = (int) Math.floor(layout.getLineLeft(line));
7194             int right = (int) Math.ceil(layout.getLineRight(line));
7195
7196             if (right - left < hspace) {
7197                 scrollx = (right + left) / 2 - hspace / 2;
7198             } else {
7199                 if (dir < 0) {
7200                     scrollx = right - hspace;
7201                 } else {
7202                     scrollx = left;
7203                 }
7204             }
7205         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7206             int right = (int) Math.ceil(layout.getLineRight(line));
7207             scrollx = right - hspace;
7208         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7209             scrollx = (int) Math.floor(layout.getLineLeft(line));
7210         }
7211
7212         if (ht < vspace) {
7213             scrolly = 0;
7214         } else {
7215             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7216                 scrolly = ht - vspace;
7217             } else {
7218                 scrolly = 0;
7219             }
7220         }
7221
7222         if (scrollx != mScrollX || scrolly != mScrollY) {
7223             scrollTo(scrollx, scrolly);
7224             return true;
7225         } else {
7226             return false;
7227         }
7228     }
7229
7230     /**
7231      * Move the point, specified by the offset, into the view if it is needed.
7232      * This has to be called after layout. Returns true if anything changed.
7233      */
7234     public boolean bringPointIntoView(int offset) {
7235         if (isLayoutRequested()) {
7236             mDeferScroll = offset;
7237             return false;
7238         }
7239         boolean changed = false;
7240
7241         Layout layout = isShowingHint() ? mHintLayout: mLayout;
7242
7243         if (layout == null) return changed;
7244
7245         int line = layout.getLineForOffset(offset);
7246
7247         int grav;
7248
7249         switch (layout.getParagraphAlignment(line)) {
7250             case ALIGN_LEFT:
7251                 grav = 1;
7252                 break;
7253             case ALIGN_RIGHT:
7254                 grav = -1;
7255                 break;
7256             case ALIGN_NORMAL:
7257                 grav = layout.getParagraphDirection(line);
7258                 break;
7259             case ALIGN_OPPOSITE:
7260                 grav = -layout.getParagraphDirection(line);
7261                 break;
7262             case ALIGN_CENTER:
7263             default:
7264                 grav = 0;
7265                 break;
7266         }
7267
7268         // We only want to clamp the cursor to fit within the layout width
7269         // in left-to-right modes, because in a right to left alignment,
7270         // we want to scroll to keep the line-right on the screen, as other
7271         // lines are likely to have text flush with the right margin, which
7272         // we want to keep visible.
7273         // A better long-term solution would probably be to measure both
7274         // the full line and a blank-trimmed version, and, for example, use
7275         // the latter measurement for centering and right alignment, but for
7276         // the time being we only implement the cursor clamping in left to
7277         // right where it is most likely to be annoying.
7278         final boolean clamped = grav > 0;
7279         // FIXME: Is it okay to truncate this, or should we round?
7280         final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7281         final int top = layout.getLineTop(line);
7282         final int bottom = layout.getLineTop(line + 1);
7283
7284         int left = (int) Math.floor(layout.getLineLeft(line));
7285         int right = (int) Math.ceil(layout.getLineRight(line));
7286         int ht = layout.getHeight();
7287
7288         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7289         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7290         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7291             // If cursor has been clamped, make sure we don't scroll.
7292             right = Math.max(x, left + hspace);
7293         }
7294
7295         int hslack = (bottom - top) / 2;
7296         int vslack = hslack;
7297
7298         if (vslack > vspace / 4)
7299             vslack = vspace / 4;
7300         if (hslack > hspace / 4)
7301             hslack = hspace / 4;
7302
7303         int hs = mScrollX;
7304         int vs = mScrollY;
7305
7306         if (top - vs < vslack)
7307             vs = top - vslack;
7308         if (bottom - vs > vspace - vslack)
7309             vs = bottom - (vspace - vslack);
7310         if (ht - vs < vspace)
7311             vs = ht - vspace;
7312         if (0 - vs > 0)
7313             vs = 0;
7314
7315         if (grav != 0) {
7316             if (x - hs < hslack) {
7317                 hs = x - hslack;
7318             }
7319             if (x - hs > hspace - hslack) {
7320                 hs = x - (hspace - hslack);
7321             }
7322         }
7323
7324         if (grav < 0) {
7325             if (left - hs > 0)
7326                 hs = left;
7327             if (right - hs < hspace)
7328                 hs = right - hspace;
7329         } else if (grav > 0) {
7330             if (right - hs < hspace)
7331                 hs = right - hspace;
7332             if (left - hs > 0)
7333                 hs = left;
7334         } else /* grav == 0 */ {
7335             if (right - left <= hspace) {
7336                 /*
7337                  * If the entire text fits, center it exactly.
7338                  */
7339                 hs = left - (hspace - (right - left)) / 2;
7340             } else if (x > right - hslack) {
7341                 /*
7342                  * If we are near the right edge, keep the right edge
7343                  * at the edge of the view.
7344                  */
7345                 hs = right - hspace;
7346             } else if (x < left + hslack) {
7347                 /*
7348                  * If we are near the left edge, keep the left edge
7349                  * at the edge of the view.
7350                  */
7351                 hs = left;
7352             } else if (left > hs) {
7353                 /*
7354                  * Is there whitespace visible at the left?  Fix it if so.
7355                  */
7356                 hs = left;
7357             } else if (right < hs + hspace) {
7358                 /*
7359                  * Is there whitespace visible at the right?  Fix it if so.
7360                  */
7361                 hs = right - hspace;
7362             } else {
7363                 /*
7364                  * Otherwise, float as needed.
7365                  */
7366                 if (x - hs < hslack) {
7367                     hs = x - hslack;
7368                 }
7369                 if (x - hs > hspace - hslack) {
7370                     hs = x - (hspace - hslack);
7371                 }
7372             }
7373         }
7374
7375         if (hs != mScrollX || vs != mScrollY) {
7376             if (mScroller == null) {
7377                 scrollTo(hs, vs);
7378             } else {
7379                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7380                 int dx = hs - mScrollX;
7381                 int dy = vs - mScrollY;
7382
7383                 if (duration > ANIMATED_SCROLL_GAP) {
7384                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7385                     awakenScrollBars(mScroller.getDuration());
7386                     invalidate();
7387                 } else {
7388                     if (!mScroller.isFinished()) {
7389                         mScroller.abortAnimation();
7390                     }
7391
7392                     scrollBy(dx, dy);
7393                 }
7394
7395                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7396             }
7397
7398             changed = true;
7399         }
7400
7401         if (isFocused()) {
7402             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7403             // requestRectangleOnScreen() is in terms of content coordinates.
7404
7405             // The offsets here are to ensure the rectangle we are using is
7406             // within our view bounds, in case the cursor is on the far left
7407             // or right.  If it isn't withing the bounds, then this request
7408             // will be ignored.
7409             if (mTempRect == null) mTempRect = new Rect();
7410             mTempRect.set(x - 2, top, x + 2, bottom);
7411             getInterestingRect(mTempRect, line);
7412             mTempRect.offset(mScrollX, mScrollY);
7413
7414             if (requestRectangleOnScreen(mTempRect)) {
7415                 changed = true;
7416             }
7417         }
7418
7419         return changed;
7420     }
7421
7422     /**
7423      * Move the cursor, if needed, so that it is at an offset that is visible
7424      * to the user.  This will not move the cursor if it represents more than
7425      * one character (a selection range).  This will only work if the
7426      * TextView contains spannable text; otherwise it will do nothing.
7427      *
7428      * @return True if the cursor was actually moved, false otherwise.
7429      */
7430     public boolean moveCursorToVisibleOffset() {
7431         if (!(mText instanceof Spannable)) {
7432             return false;
7433         }
7434         int start = getSelectionStart();
7435         int end = getSelectionEnd();
7436         if (start != end) {
7437             return false;
7438         }
7439
7440         // First: make sure the line is visible on screen:
7441
7442         int line = mLayout.getLineForOffset(start);
7443
7444         final int top = mLayout.getLineTop(line);
7445         final int bottom = mLayout.getLineTop(line + 1);
7446         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7447         int vslack = (bottom - top) / 2;
7448         if (vslack > vspace / 4)
7449             vslack = vspace / 4;
7450         final int vs = mScrollY;
7451
7452         if (top < (vs+vslack)) {
7453             line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7454         } else if (bottom > (vspace+vs-vslack)) {
7455             line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7456         }
7457
7458         // Next: make sure the character is visible on screen:
7459
7460         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7461         final int hs = mScrollX;
7462         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7463         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7464
7465         // line might contain bidirectional text
7466         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7467         final int highChar = leftChar > rightChar ? leftChar : rightChar;
7468
7469         int newStart = start;
7470         if (newStart < lowChar) {
7471             newStart = lowChar;
7472         } else if (newStart > highChar) {
7473             newStart = highChar;
7474         }
7475
7476         if (newStart != start) {
7477             Selection.setSelection((Spannable)mText, newStart);
7478             return true;
7479         }
7480
7481         return false;
7482     }
7483
7484     @Override
7485     public void computeScroll() {
7486         if (mScroller != null) {
7487             if (mScroller.computeScrollOffset()) {
7488                 mScrollX = mScroller.getCurrX();
7489                 mScrollY = mScroller.getCurrY();
7490                 invalidateParentCaches();
7491                 postInvalidate();  // So we draw again
7492             }
7493         }
7494     }
7495
7496     private void getInterestingRect(Rect r, int line) {
7497         convertFromViewportToContentCoordinates(r);
7498
7499         // Rectangle can can be expanded on first and last line to take
7500         // padding into account.
7501         // TODO Take left/right padding into account too?
7502         if (line == 0) r.top -= getExtendedPaddingTop();
7503         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7504     }
7505
7506     private void convertFromViewportToContentCoordinates(Rect r) {
7507         final int horizontalOffset = viewportToContentHorizontalOffset();
7508         r.left += horizontalOffset;
7509         r.right += horizontalOffset;
7510
7511         final int verticalOffset = viewportToContentVerticalOffset();
7512         r.top += verticalOffset;
7513         r.bottom += verticalOffset;
7514     }
7515
7516     int viewportToContentHorizontalOffset() {
7517         return getCompoundPaddingLeft() - mScrollX;
7518     }
7519
7520     int viewportToContentVerticalOffset() {
7521         int offset = getExtendedPaddingTop() - mScrollY;
7522         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7523             offset += getVerticalOffset(false);
7524         }
7525         return offset;
7526     }
7527
7528     @Override
7529     public void debug(int depth) {
7530         super.debug(depth);
7531
7532         String output = debugIndent(depth);
7533         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7534                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7535                 + "} ";
7536
7537         if (mText != null) {
7538
7539             output += "mText=\"" + mText + "\" ";
7540             if (mLayout != null) {
7541                 output += "mLayout width=" + mLayout.getWidth()
7542                         + " height=" + mLayout.getHeight();
7543             }
7544         } else {
7545             output += "mText=NULL";
7546         }
7547         Log.d(VIEW_LOG_TAG, output);
7548     }
7549
7550     /**
7551      * Convenience for {@link Selection#getSelectionStart}.
7552      */
7553     @ViewDebug.ExportedProperty(category = "text")
7554     public int getSelectionStart() {
7555         return Selection.getSelectionStart(getText());
7556     }
7557
7558     /**
7559      * Convenience for {@link Selection#getSelectionEnd}.
7560      */
7561     @ViewDebug.ExportedProperty(category = "text")
7562     public int getSelectionEnd() {
7563         return Selection.getSelectionEnd(getText());
7564     }
7565
7566     /**
7567      * Return true iff there is a selection inside this text view.
7568      */
7569     public boolean hasSelection() {
7570         final int selectionStart = getSelectionStart();
7571         final int selectionEnd = getSelectionEnd();
7572
7573         return selectionStart >= 0 && selectionStart != selectionEnd;
7574     }
7575
7576     String getSelectedText() {
7577         if (hasSelection()) {
7578             return String.valueOf(mText.subSequence(getSelectionStart(), getSelectionEnd()));
7579         }
7580         return null;
7581     }
7582
7583     /**
7584      * Sets the properties of this field (lines, horizontally scrolling,
7585      * transformation method) to be for a single-line input.
7586      *
7587      * @attr ref android.R.styleable#TextView_singleLine
7588      */
7589     public void setSingleLine() {
7590         setSingleLine(true);
7591     }
7592
7593     /**
7594      * Sets the properties of this field to transform input to ALL CAPS
7595      * display. This may use a "small caps" formatting if available.
7596      * This setting will be ignored if this field is editable or selectable.
7597      *
7598      * This call replaces the current transformation method. Disabling this
7599      * will not necessarily restore the previous behavior from before this
7600      * was enabled.
7601      *
7602      * @see #setTransformationMethod(TransformationMethod)
7603      * @attr ref android.R.styleable#TextView_textAllCaps
7604      */
7605     public void setAllCaps(boolean allCaps) {
7606         if (allCaps) {
7607             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7608         } else {
7609             setTransformationMethod(null);
7610         }
7611     }
7612
7613     /**
7614      * If true, sets the properties of this field (number of lines, horizontally scrolling,
7615      * transformation method) to be for a single-line input; if false, restores these to the default
7616      * conditions.
7617      *
7618      * Note that the default conditions are not necessarily those that were in effect prior this
7619      * method, and you may want to reset these properties to your custom values.
7620      *
7621      * @attr ref android.R.styleable#TextView_singleLine
7622      */
7623     @android.view.RemotableViewMethod
7624     public void setSingleLine(boolean singleLine) {
7625         // Could be used, but may break backward compatibility.
7626         // if (mSingleLine == singleLine) return;
7627         setInputTypeSingleLine(singleLine);
7628         applySingleLine(singleLine, true, true);
7629     }
7630
7631     /**
7632      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7633      * @param singleLine
7634      */
7635     private void setInputTypeSingleLine(boolean singleLine) {
7636         if (mEditor != null &&
7637                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7638             if (singleLine) {
7639                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7640             } else {
7641                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7642             }
7643         }
7644     }
7645
7646     private void applySingleLine(boolean singleLine, boolean applyTransformation,
7647             boolean changeMaxLines) {
7648         mSingleLine = singleLine;
7649         if (singleLine) {
7650             setLines(1);
7651             setHorizontallyScrolling(true);
7652             if (applyTransformation) {
7653                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7654             }
7655         } else {
7656             if (changeMaxLines) {
7657                 setMaxLines(Integer.MAX_VALUE);
7658             }
7659             setHorizontallyScrolling(false);
7660             if (applyTransformation) {
7661                 setTransformationMethod(null);
7662             }
7663         }
7664     }
7665
7666     /**
7667      * Causes words in the text that are longer than the view is wide
7668      * to be ellipsized instead of broken in the middle.  You may also
7669      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7670      * to constrain the text to a single line.  Use <code>null</code>
7671      * to turn off ellipsizing.
7672      *
7673      * If {@link #setMaxLines} has been used to set two or more lines,
7674      * only {@link android.text.TextUtils.TruncateAt#END} and
7675      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7676      * (other ellipsizing types will not do anything).
7677      *
7678      * @attr ref android.R.styleable#TextView_ellipsize
7679      */
7680     public void setEllipsize(TextUtils.TruncateAt where) {
7681         // TruncateAt is an enum. != comparison is ok between these singleton objects.
7682         if (mEllipsize != where) {
7683             mEllipsize = where;
7684
7685             if (mLayout != null) {
7686                 nullLayouts();
7687                 requestLayout();
7688                 invalidate();
7689             }
7690         }
7691     }
7692
7693     /**
7694      * Sets how many times to repeat the marquee animation. Only applied if the
7695      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7696      *
7697      * @see #getMarqueeRepeatLimit()
7698      *
7699      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7700      */
7701     public void setMarqueeRepeatLimit(int marqueeLimit) {
7702         mMarqueeRepeatLimit = marqueeLimit;
7703     }
7704
7705     /**
7706      * Gets the number of times the marquee animation is repeated. Only meaningful if the
7707      * TextView has marquee enabled.
7708      *
7709      * @return the number of times the marquee animation is repeated. -1 if the animation
7710      * repeats indefinitely
7711      *
7712      * @see #setMarqueeRepeatLimit(int)
7713      *
7714      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7715      */
7716     public int getMarqueeRepeatLimit() {
7717         return mMarqueeRepeatLimit;
7718     }
7719
7720     /**
7721      * Returns where, if anywhere, words that are longer than the view
7722      * is wide should be ellipsized.
7723      */
7724     @ViewDebug.ExportedProperty
7725     public TextUtils.TruncateAt getEllipsize() {
7726         return mEllipsize;
7727     }
7728
7729     /**
7730      * Set the TextView so that when it takes focus, all the text is
7731      * selected.
7732      *
7733      * @attr ref android.R.styleable#TextView_selectAllOnFocus
7734      */
7735     @android.view.RemotableViewMethod
7736     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7737         createEditorIfNeeded();
7738         mEditor.mSelectAllOnFocus = selectAllOnFocus;
7739
7740         if (selectAllOnFocus && !(mText instanceof Spannable)) {
7741             setText(mText, BufferType.SPANNABLE);
7742         }
7743     }
7744
7745     /**
7746      * Set whether the cursor is visible. The default is true. Note that this property only
7747      * makes sense for editable TextView.
7748      *
7749      * @see #isCursorVisible()
7750      *
7751      * @attr ref android.R.styleable#TextView_cursorVisible
7752      */
7753     @android.view.RemotableViewMethod
7754     public void setCursorVisible(boolean visible) {
7755         if (visible && mEditor == null) return; // visible is the default value with no edit data
7756         createEditorIfNeeded();
7757         if (mEditor.mCursorVisible != visible) {
7758             mEditor.mCursorVisible = visible;
7759             invalidate();
7760
7761             mEditor.makeBlink();
7762
7763             // InsertionPointCursorController depends on mCursorVisible
7764             mEditor.prepareCursorControllers();
7765         }
7766     }
7767
7768     /**
7769      * @return whether or not the cursor is visible (assuming this TextView is editable)
7770      *
7771      * @see #setCursorVisible(boolean)
7772      *
7773      * @attr ref android.R.styleable#TextView_cursorVisible
7774      */
7775     public boolean isCursorVisible() {
7776         // true is the default value
7777         return mEditor == null ? true : mEditor.mCursorVisible;
7778     }
7779
7780     private boolean canMarquee() {
7781         int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7782         return width > 0 && (mLayout.getLineWidth(0) > width ||
7783                 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7784                         mSavedMarqueeModeLayout.getLineWidth(0) > width));
7785     }
7786
7787     private void startMarquee() {
7788         // Do not ellipsize EditText
7789         if (getKeyListener() != null) return;
7790
7791         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7792             return;
7793         }
7794
7795         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7796                 getLineCount() == 1 && canMarquee()) {
7797
7798             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7799                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7800                 final Layout tmp = mLayout;
7801                 mLayout = mSavedMarqueeModeLayout;
7802                 mSavedMarqueeModeLayout = tmp;
7803                 setHorizontalFadingEdgeEnabled(true);
7804                 requestLayout();
7805                 invalidate();
7806             }
7807
7808             if (mMarquee == null) mMarquee = new Marquee(this);
7809             mMarquee.start(mMarqueeRepeatLimit);
7810         }
7811     }
7812
7813     private void stopMarquee() {
7814         if (mMarquee != null && !mMarquee.isStopped()) {
7815             mMarquee.stop();
7816         }
7817
7818         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7819             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7820             final Layout tmp = mSavedMarqueeModeLayout;
7821             mSavedMarqueeModeLayout = mLayout;
7822             mLayout = tmp;
7823             setHorizontalFadingEdgeEnabled(false);
7824             requestLayout();
7825             invalidate();
7826         }
7827     }
7828
7829     private void startStopMarquee(boolean start) {
7830         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7831             if (start) {
7832                 startMarquee();
7833             } else {
7834                 stopMarquee();
7835             }
7836         }
7837     }
7838
7839     /**
7840      * This method is called when the text is changed, in case any subclasses
7841      * would like to know.
7842      *
7843      * Within <code>text</code>, the <code>lengthAfter</code> characters
7844      * beginning at <code>start</code> have just replaced old text that had
7845      * length <code>lengthBefore</code>. It is an error to attempt to make
7846      * changes to <code>text</code> from this callback.
7847      *
7848      * @param text The text the TextView is displaying
7849      * @param start The offset of the start of the range of the text that was
7850      * modified
7851      * @param lengthBefore The length of the former text that has been replaced
7852      * @param lengthAfter The length of the replacement modified text
7853      */
7854     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7855         // intentionally empty, template pattern method can be overridden by subclasses
7856     }
7857
7858     /**
7859      * This method is called when the selection has changed, in case any
7860      * subclasses would like to know.
7861      *
7862      * @param selStart The new selection start location.
7863      * @param selEnd The new selection end location.
7864      */
7865     protected void onSelectionChanged(int selStart, int selEnd) {
7866         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7867     }
7868
7869     /**
7870      * Adds a TextWatcher to the list of those whose methods are called
7871      * whenever this TextView's text changes.
7872      * <p>
7873      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7874      * not called after {@link #setText} calls.  Now, doing {@link #setText}
7875      * if there are any text changed listeners forces the buffer type to
7876      * Editable if it would not otherwise be and does call this method.
7877      */
7878     public void addTextChangedListener(TextWatcher watcher) {
7879         if (mListeners == null) {
7880             mListeners = new ArrayList<TextWatcher>();
7881         }
7882
7883         mListeners.add(watcher);
7884     }
7885
7886     /**
7887      * Removes the specified TextWatcher from the list of those whose
7888      * methods are called
7889      * whenever this TextView's text changes.
7890      */
7891     public void removeTextChangedListener(TextWatcher watcher) {
7892         if (mListeners != null) {
7893             int i = mListeners.indexOf(watcher);
7894
7895             if (i >= 0) {
7896                 mListeners.remove(i);
7897             }
7898         }
7899     }
7900
7901     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7902         if (mListeners != null) {
7903             final ArrayList<TextWatcher> list = mListeners;
7904             final int count = list.size();
7905             for (int i = 0; i < count; i++) {
7906                 list.get(i).beforeTextChanged(text, start, before, after);
7907             }
7908         }
7909
7910         // The spans that are inside or intersect the modified region no longer make sense
7911         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7912         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
7913     }
7914
7915     // Removes all spans that are inside or actually overlap the start..end range
7916     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
7917         if (!(mText instanceof Editable)) return;
7918         Editable text = (Editable) mText;
7919
7920         T[] spans = text.getSpans(start, end, type);
7921         final int length = spans.length;
7922         for (int i = 0; i < length; i++) {
7923             final int spanStart = text.getSpanStart(spans[i]);
7924             final int spanEnd = text.getSpanEnd(spans[i]);
7925             if (spanEnd == start || spanStart == end) break;
7926             text.removeSpan(spans[i]);
7927         }
7928     }
7929
7930     void removeAdjacentSuggestionSpans(final int pos) {
7931         if (!(mText instanceof Editable)) return;
7932         final Editable text = (Editable) mText;
7933
7934         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7935         final int length = spans.length;
7936         for (int i = 0; i < length; i++) {
7937             final int spanStart = text.getSpanStart(spans[i]);
7938             final int spanEnd = text.getSpanEnd(spans[i]);
7939             if (spanEnd == pos || spanStart == pos) {
7940                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7941                     text.removeSpan(spans[i]);
7942                 }
7943             }
7944         }
7945     }
7946
7947     /**
7948      * Not private so it can be called from an inner class without going
7949      * through a thunk.
7950      */
7951     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7952         if (mListeners != null) {
7953             final ArrayList<TextWatcher> list = mListeners;
7954             final int count = list.size();
7955             for (int i = 0; i < count; i++) {
7956                 list.get(i).onTextChanged(text, start, before, after);
7957             }
7958         }
7959
7960         if (mEditor != null) mEditor.sendOnTextChanged(start, after);
7961     }
7962
7963     /**
7964      * Not private so it can be called from an inner class without going
7965      * through a thunk.
7966      */
7967     void sendAfterTextChanged(Editable text) {
7968         if (mListeners != null) {
7969             final ArrayList<TextWatcher> list = mListeners;
7970             final int count = list.size();
7971             for (int i = 0; i < count; i++) {
7972                 list.get(i).afterTextChanged(text);
7973             }
7974         }
7975         hideErrorIfUnchanged();
7976     }
7977
7978     void updateAfterEdit() {
7979         invalidate();
7980         int curs = getSelectionStart();
7981
7982         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7983             registerForPreDraw();
7984         }
7985
7986         checkForResize();
7987
7988         if (curs >= 0) {
7989             mHighlightPathBogus = true;
7990             if (mEditor != null) mEditor.makeBlink();
7991             bringPointIntoView(curs);
7992         }
7993     }
7994
7995     /**
7996      * Not private so it can be called from an inner class without going
7997      * through a thunk.
7998      */
7999     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8000         sLastCutCopyOrTextChangedTime = 0;
8001
8002         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8003         if (ims == null || ims.mBatchEditNesting == 0) {
8004             updateAfterEdit();
8005         }
8006         if (ims != null) {
8007             ims.mContentChanged = true;
8008             if (ims.mChangedStart < 0) {
8009                 ims.mChangedStart = start;
8010                 ims.mChangedEnd = start+before;
8011             } else {
8012                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8013                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8014             }
8015             ims.mChangedDelta += after-before;
8016         }
8017         resetErrorChangedFlag();
8018         sendOnTextChanged(buffer, start, before, after);
8019         onTextChanged(buffer, start, before, after);
8020     }
8021
8022     /**
8023      * Not private so it can be called from an inner class without going
8024      * through a thunk.
8025      */
8026     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8027         // XXX Make the start and end move together if this ends up
8028         // spending too much time invalidating.
8029
8030         boolean selChanged = false;
8031         int newSelStart=-1, newSelEnd=-1;
8032
8033         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8034
8035         if (what == Selection.SELECTION_END) {
8036             selChanged = true;
8037             newSelEnd = newStart;
8038
8039             if (oldStart >= 0 || newStart >= 0) {
8040                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8041                 checkForResize();
8042                 registerForPreDraw();
8043                 if (mEditor != null) mEditor.makeBlink();
8044             }
8045         }
8046
8047         if (what == Selection.SELECTION_START) {
8048             selChanged = true;
8049             newSelStart = newStart;
8050
8051             if (oldStart >= 0 || newStart >= 0) {
8052                 int end = Selection.getSelectionEnd(buf);
8053                 invalidateCursor(end, oldStart, newStart);
8054             }
8055         }
8056
8057         if (selChanged) {
8058             mHighlightPathBogus = true;
8059             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8060
8061             if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8062                 if (newSelStart < 0) {
8063                     newSelStart = Selection.getSelectionStart(buf);
8064                 }
8065                 if (newSelEnd < 0) {
8066                     newSelEnd = Selection.getSelectionEnd(buf);
8067                 }
8068                 onSelectionChanged(newSelStart, newSelEnd);
8069             }
8070         }
8071
8072         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8073                 what instanceof CharacterStyle) {
8074             if (ims == null || ims.mBatchEditNesting == 0) {
8075                 invalidate();
8076                 mHighlightPathBogus = true;
8077                 checkForResize();
8078             } else {
8079                 ims.mContentChanged = true;
8080             }
8081             if (mEditor != null) {
8082                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8083                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8084             }
8085         }
8086
8087         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8088             mHighlightPathBogus = true;
8089             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8090                 ims.mSelectionModeChanged = true;
8091             }
8092
8093             if (Selection.getSelectionStart(buf) >= 0) {
8094                 if (ims == null || ims.mBatchEditNesting == 0) {
8095                     invalidateCursor();
8096                 } else {
8097                     ims.mCursorChanged = true;
8098                 }
8099             }
8100         }
8101
8102         if (what instanceof ParcelableSpan) {
8103             // If this is a span that can be sent to a remote process,
8104             // the current extract editor would be interested in it.
8105             if (ims != null && ims.mExtractedTextRequest != null) {
8106                 if (ims.mBatchEditNesting != 0) {
8107                     if (oldStart >= 0) {
8108                         if (ims.mChangedStart > oldStart) {
8109                             ims.mChangedStart = oldStart;
8110                         }
8111                         if (ims.mChangedStart > oldEnd) {
8112                             ims.mChangedStart = oldEnd;
8113                         }
8114                     }
8115                     if (newStart >= 0) {
8116                         if (ims.mChangedStart > newStart) {
8117                             ims.mChangedStart = newStart;
8118                         }
8119                         if (ims.mChangedStart > newEnd) {
8120                             ims.mChangedStart = newEnd;
8121                         }
8122                     }
8123                 } else {
8124                     if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8125                             + oldStart + "-" + oldEnd + ","
8126                             + newStart + "-" + newEnd + " " + what);
8127                     ims.mContentChanged = true;
8128                 }
8129             }
8130         }
8131
8132         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8133                 what instanceof SpellCheckSpan) {
8134             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8135         }
8136     }
8137
8138     /**
8139      * @hide
8140      */
8141     @Override
8142     public void dispatchFinishTemporaryDetach() {
8143         mDispatchTemporaryDetach = true;
8144         super.dispatchFinishTemporaryDetach();
8145         mDispatchTemporaryDetach = false;
8146     }
8147
8148     @Override
8149     public void onStartTemporaryDetach() {
8150         super.onStartTemporaryDetach();
8151         // Only track when onStartTemporaryDetach() is called directly,
8152         // usually because this instance is an editable field in a list
8153         if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
8154
8155         // Tell the editor that we are temporarily detached. It can use this to preserve
8156         // selection state as needed.
8157         if (mEditor != null) mEditor.mTemporaryDetach = true;
8158     }
8159
8160     @Override
8161     public void onFinishTemporaryDetach() {
8162         super.onFinishTemporaryDetach();
8163         // Only track when onStartTemporaryDetach() is called directly,
8164         // usually because this instance is an editable field in a list
8165         if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
8166         if (mEditor != null) mEditor.mTemporaryDetach = false;
8167     }
8168
8169     @Override
8170     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8171         if (mTemporaryDetach) {
8172             // If we are temporarily in the detach state, then do nothing.
8173             super.onFocusChanged(focused, direction, previouslyFocusedRect);
8174             return;
8175         }
8176
8177         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8178
8179         if (focused) {
8180             if (mText instanceof Spannable) {
8181                 Spannable sp = (Spannable) mText;
8182                 MetaKeyKeyListener.resetMetaState(sp);
8183             }
8184         }
8185
8186         startStopMarquee(focused);
8187
8188         if (mTransformation != null) {
8189             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8190         }
8191
8192         super.onFocusChanged(focused, direction, previouslyFocusedRect);
8193     }
8194
8195     @Override
8196     public void onWindowFocusChanged(boolean hasWindowFocus) {
8197         super.onWindowFocusChanged(hasWindowFocus);
8198
8199         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8200
8201         startStopMarquee(hasWindowFocus);
8202     }
8203
8204     @Override
8205     protected void onVisibilityChanged(View changedView, int visibility) {
8206         super.onVisibilityChanged(changedView, visibility);
8207         if (mEditor != null && visibility != VISIBLE) {
8208             mEditor.hideCursorAndSpanControllers();
8209             stopTextActionMode();
8210         }
8211     }
8212
8213     /**
8214      * Use {@link BaseInputConnection#removeComposingSpans
8215      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8216      * state from this text view.
8217      */
8218     public void clearComposingText() {
8219         if (mText instanceof Spannable) {
8220             BaseInputConnection.removeComposingSpans((Spannable)mText);
8221         }
8222     }
8223
8224     @Override
8225     public void setSelected(boolean selected) {
8226         boolean wasSelected = isSelected();
8227
8228         super.setSelected(selected);
8229
8230         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8231             if (selected) {
8232                 startMarquee();
8233             } else {
8234                 stopMarquee();
8235             }
8236         }
8237     }
8238
8239     @Override
8240     public boolean onTouchEvent(MotionEvent event) {
8241         final int action = event.getActionMasked();
8242
8243         if (mEditor != null && action == MotionEvent.ACTION_DOWN) {
8244             // Detect double tap and inform the Editor.
8245             if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
8246                     ViewConfiguration.getDoubleTapTimeout()) {
8247                 mEditor.mDoubleTap = true;
8248                 mFirstTouch = false;
8249             } else {
8250                 mEditor.mDoubleTap = false;
8251                 mFirstTouch = true;
8252             }
8253         }
8254
8255         if (action == MotionEvent.ACTION_UP) {
8256             mLastTouchUpTime = SystemClock.uptimeMillis();
8257         }
8258
8259         if (mEditor != null) {
8260             mEditor.onTouchEvent(event);
8261
8262             if (mEditor.mSelectionModifierCursorController != null &&
8263                     mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8264                 return true;
8265             }
8266         }
8267
8268         final boolean superResult = super.onTouchEvent(event);
8269
8270         /*
8271          * Don't handle the release after a long press, because it will move the selection away from
8272          * whatever the menu action was trying to affect. If the long press should have triggered an
8273          * insertion action mode, we can now actually show it.
8274          */
8275         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8276             mEditor.mDiscardNextActionUp = false;
8277
8278             if (mEditor.mIsInsertionActionModeStartPending) {
8279                 mEditor.startInsertionActionMode();
8280                 mEditor.mIsInsertionActionModeStartPending = false;
8281             }
8282             return superResult;
8283         }
8284
8285         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8286                 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8287
8288          if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8289                 && mText instanceof Spannable && mLayout != null) {
8290             boolean handled = false;
8291
8292             if (mMovement != null) {
8293                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8294             }
8295
8296             final boolean textIsSelectable = isTextSelectable();
8297             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8298                 // The LinkMovementMethod which should handle taps on links has not been installed
8299                 // on non editable text that support text selection.
8300                 // We reproduce its behavior here to open links for these.
8301                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8302                         getSelectionEnd(), ClickableSpan.class);
8303
8304                 if (links.length > 0) {
8305                     links[0].onClick(this);
8306                     handled = true;
8307                 }
8308             }
8309
8310             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8311                 // Show the IME, except when selecting in read-only text.
8312                 final InputMethodManager imm = InputMethodManager.peekInstance();
8313                 viewClicked(imm);
8314                 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8315                     handled |= imm != null && imm.showSoftInput(this, 0);
8316                 }
8317
8318                 // The above condition ensures that the mEditor is not null
8319                 mEditor.onTouchUpEvent(event);
8320
8321                 handled = true;
8322             }
8323
8324             if (handled) {
8325                 return true;
8326             }
8327         }
8328
8329         return superResult;
8330     }
8331
8332     @Override
8333     public boolean onGenericMotionEvent(MotionEvent event) {
8334         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8335             try {
8336                 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8337                     return true;
8338                 }
8339             } catch (AbstractMethodError ex) {
8340                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8341                 // Ignore its absence in case third party applications implemented the
8342                 // interface directly.
8343             }
8344         }
8345         return super.onGenericMotionEvent(event);
8346     }
8347
8348     /**
8349      * @return True iff this TextView contains a text that can be edited, or if this is
8350      * a selectable TextView.
8351      */
8352     boolean isTextEditable() {
8353         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8354     }
8355
8356     /**
8357      * Returns true, only while processing a touch gesture, if the initial
8358      * touch down event caused focus to move to the text view and as a result
8359      * its selection changed.  Only valid while processing the touch gesture
8360      * of interest, in an editable text view.
8361      */
8362     public boolean didTouchFocusSelect() {
8363         return mEditor != null && mEditor.mTouchFocusSelected;
8364     }
8365
8366     @Override
8367     public void cancelLongPress() {
8368         super.cancelLongPress();
8369         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8370     }
8371
8372     @Override
8373     public boolean onTrackballEvent(MotionEvent event) {
8374         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8375             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8376                 return true;
8377             }
8378         }
8379
8380         return super.onTrackballEvent(event);
8381     }
8382
8383     public void setScroller(Scroller s) {
8384         mScroller = s;
8385     }
8386
8387     @Override
8388     protected float getLeftFadingEdgeStrength() {
8389         if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8390                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8391             if (mMarquee != null && !mMarquee.isStopped()) {
8392                 final Marquee marquee = mMarquee;
8393                 if (marquee.shouldDrawLeftFade()) {
8394                     final float scroll = marquee.getScroll();
8395                     return scroll / getHorizontalFadingEdgeLength();
8396                 } else {
8397                     return 0.0f;
8398                 }
8399             } else if (getLineCount() == 1) {
8400                 final int layoutDirection = getLayoutDirection();
8401                 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8402                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8403                     case Gravity.LEFT:
8404                         return 0.0f;
8405                     case Gravity.RIGHT:
8406                         return (mLayout.getLineRight(0) - (mRight - mLeft) -
8407                                 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8408                                 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8409                     case Gravity.CENTER_HORIZONTAL:
8410                     case Gravity.FILL_HORIZONTAL:
8411                         final int textDirection = mLayout.getParagraphDirection(0);
8412                         if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8413                             return 0.0f;
8414                         } else {
8415                             return (mLayout.getLineRight(0) - (mRight - mLeft) -
8416                                 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8417                                 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8418                         }
8419                 }
8420             }
8421         }
8422         return super.getLeftFadingEdgeStrength();
8423     }
8424
8425     @Override
8426     protected float getRightFadingEdgeStrength() {
8427         if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8428                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8429             if (mMarquee != null && !mMarquee.isStopped()) {
8430                 final Marquee marquee = mMarquee;
8431                 final float maxFadeScroll = marquee.getMaxFadeScroll();
8432                 final float scroll = marquee.getScroll();
8433                 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8434             } else if (getLineCount() == 1) {
8435                 final int layoutDirection = getLayoutDirection();
8436                 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8437                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8438                     case Gravity.LEFT:
8439                         final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8440                                 getCompoundPaddingRight();
8441                         final float lineWidth = mLayout.getLineWidth(0);
8442                         return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8443                     case Gravity.RIGHT:
8444                         return 0.0f;
8445                     case Gravity.CENTER_HORIZONTAL:
8446                     case Gravity.FILL_HORIZONTAL:
8447                         final int textDirection = mLayout.getParagraphDirection(0);
8448                         if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8449                             return 0.0f;
8450                         } else {
8451                             return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8452                                 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8453                                 getHorizontalFadingEdgeLength();
8454                         }
8455                 }
8456             }
8457         }
8458         return super.getRightFadingEdgeStrength();
8459     }
8460
8461     @Override
8462     protected int computeHorizontalScrollRange() {
8463         if (mLayout != null) {
8464             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8465                     (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8466         }
8467
8468         return super.computeHorizontalScrollRange();
8469     }
8470
8471     @Override
8472     protected int computeVerticalScrollRange() {
8473         if (mLayout != null)
8474             return mLayout.getHeight();
8475
8476         return super.computeVerticalScrollRange();
8477     }
8478
8479     @Override
8480     protected int computeVerticalScrollExtent() {
8481         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8482     }
8483
8484     @Override
8485     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8486         super.findViewsWithText(outViews, searched, flags);
8487         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8488                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8489             String searchedLowerCase = searched.toString().toLowerCase();
8490             String textLowerCase = mText.toString().toLowerCase();
8491             if (textLowerCase.contains(searchedLowerCase)) {
8492                 outViews.add(this);
8493             }
8494         }
8495     }
8496
8497     public enum BufferType {
8498         NORMAL, SPANNABLE, EDITABLE,
8499     }
8500
8501     /**
8502      * Returns the TextView_textColor attribute from the TypedArray, if set, or
8503      * the TextAppearance_textColor from the TextView_textAppearance attribute,
8504      * if TextView_textColor was not set directly.
8505      *
8506      * @removed
8507      */
8508     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8509         if (attrs == null) {
8510             // Preserve behavior prior to removal of this API.
8511             throw new NullPointerException();
8512         }
8513
8514         // It's not safe to use this method from apps. The parameter 'attrs'
8515         // must have been obtained using the TextView filter array which is not
8516         // available to the SDK. As such, we grab a default TypedArray with the
8517         // right filter instead here.
8518         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8519         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8520         if (colors == null) {
8521             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8522             if (ap != 0) {
8523                 final TypedArray appearance = context.obtainStyledAttributes(
8524                         ap, R.styleable.TextAppearance);
8525                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8526                 appearance.recycle();
8527             }
8528         }
8529         a.recycle();
8530
8531         return colors;
8532     }
8533
8534     /**
8535      * Returns the default color from the TextView_textColor attribute from the
8536      * AttributeSet, if set, or the default color from the
8537      * TextAppearance_textColor from the TextView_textAppearance attribute, if
8538      * TextView_textColor was not set directly.
8539      *
8540      * @removed
8541      */
8542     public static int getTextColor(Context context, TypedArray attrs, int def) {
8543         final ColorStateList colors = getTextColors(context, attrs);
8544         if (colors == null) {
8545             return def;
8546         } else {
8547             return colors.getDefaultColor();
8548         }
8549     }
8550
8551     @Override
8552     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8553         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8554             // Handle Ctrl-only shortcuts.
8555             switch (keyCode) {
8556             case KeyEvent.KEYCODE_A:
8557                 if (canSelectText()) {
8558                     return onTextContextMenuItem(ID_SELECT_ALL);
8559                 }
8560                 break;
8561             case KeyEvent.KEYCODE_Z:
8562                 if (canUndo()) {
8563                     return onTextContextMenuItem(ID_UNDO);
8564                 }
8565                 break;
8566             case KeyEvent.KEYCODE_X:
8567                 if (canCut()) {
8568                     return onTextContextMenuItem(ID_CUT);
8569                 }
8570                 break;
8571             case KeyEvent.KEYCODE_C:
8572                 if (canCopy()) {
8573                     return onTextContextMenuItem(ID_COPY);
8574                 }
8575                 break;
8576             case KeyEvent.KEYCODE_V:
8577                 if (canPaste()) {
8578                     return onTextContextMenuItem(ID_PASTE);
8579                 }
8580                 break;
8581             }
8582         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8583             // Handle Ctrl-Shift shortcuts.
8584             switch (keyCode) {
8585                 case KeyEvent.KEYCODE_Z:
8586                     if (canRedo()) {
8587                         return onTextContextMenuItem(ID_REDO);
8588                     }
8589                     break;
8590                 case KeyEvent.KEYCODE_V:
8591                     if (canPaste()) {
8592                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8593                     }
8594             }
8595         }
8596         return super.onKeyShortcut(keyCode, event);
8597     }
8598
8599     /**
8600      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8601      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8602      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8603      * sufficient.
8604      */
8605     boolean canSelectText() {
8606         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8607     }
8608
8609     /**
8610      * Test based on the <i>intrinsic</i> charateristics of the TextView.
8611      * The text must be spannable and the movement method must allow for arbitary selection.
8612      *
8613      * See also {@link #canSelectText()}.
8614      */
8615     boolean textCanBeSelected() {
8616         // prepareCursorController() relies on this method.
8617         // If you change this condition, make sure prepareCursorController is called anywhere
8618         // the value of this condition might be changed.
8619         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8620         return isTextEditable() ||
8621                 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8622     }
8623
8624     private Locale getTextServicesLocale(boolean allowNullLocale) {
8625         // Start fetching the text services locale asynchronously.
8626         updateTextServicesLocaleAsync();
8627         // If !allowNullLocale and there is no cached text services locale, just return the default
8628         // locale.
8629         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8630                 : mCurrentSpellCheckerLocaleCache;
8631     }
8632
8633     /**
8634      * This is a temporary method. Future versions may support multi-locale text.
8635      * Caveat: This method may not return the latest text services locale, but this should be
8636      * acceptable and it's more important to make this method asynchronous.
8637      *
8638      * @return The locale that should be used for a word iterator
8639      * in this TextView, based on the current spell checker settings,
8640      * the current IME's locale, or the system default locale.
8641      * Please note that a word iterator in this TextView is different from another word iterator
8642      * used by SpellChecker.java of TextView. This method should be used for the former.
8643      * @hide
8644      */
8645     // TODO: Support multi-locale
8646     // TODO: Update the text services locale immediately after the keyboard locale is switched
8647     // by catching intent of keyboard switch event
8648     public Locale getTextServicesLocale() {
8649         return getTextServicesLocale(false /* allowNullLocale */);
8650     }
8651
8652     /**
8653      * @return true if this TextView is specialized for showing and interacting with the extracted
8654      * text in a full-screen input method.
8655      * @hide
8656      */
8657     public boolean isInExtractedMode() {
8658         return false;
8659     }
8660
8661     /**
8662      * This is a temporary method. Future versions may support multi-locale text.
8663      * Caveat: This method may not return the latest spell checker locale, but this should be
8664      * acceptable and it's more important to make this method asynchronous.
8665      *
8666      * @return The locale that should be used for a spell checker in this TextView,
8667      * based on the current spell checker settings, the current IME's locale, or the system default
8668      * locale.
8669      * @hide
8670      */
8671     public Locale getSpellCheckerLocale() {
8672         return getTextServicesLocale(true /* allowNullLocale */);
8673     }
8674
8675     private void updateTextServicesLocaleAsync() {
8676         // AsyncTask.execute() uses a serial executor which means we don't have
8677         // to lock around updateTextServicesLocaleLocked() to prevent it from
8678         // being executed n times in parallel.
8679         AsyncTask.execute(new Runnable() {
8680             @Override
8681             public void run() {
8682                 updateTextServicesLocaleLocked();
8683             }
8684         });
8685     }
8686
8687     private void updateTextServicesLocaleLocked() {
8688         final TextServicesManager textServicesManager = (TextServicesManager)
8689                 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8690         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8691         final Locale locale;
8692         if (subtype != null) {
8693             locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8694         } else {
8695             locale = null;
8696         }
8697         mCurrentSpellCheckerLocaleCache = locale;
8698     }
8699
8700     void onLocaleChanged() {
8701         // Will be re-created on demand in getWordIterator with the proper new locale
8702         mEditor.mWordIterator = null;
8703     }
8704
8705     /**
8706      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8707      * Made available to achieve a consistent behavior.
8708      * @hide
8709      */
8710     public WordIterator getWordIterator() {
8711         if (mEditor != null) {
8712             return mEditor.getWordIterator();
8713         } else {
8714             return null;
8715         }
8716     }
8717
8718     /** @hide */
8719     @Override
8720     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8721         super.onPopulateAccessibilityEventInternal(event);
8722
8723         final boolean isPassword = hasPasswordTransformationMethod();
8724         if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8725             final CharSequence text = getTextForAccessibility();
8726             if (!TextUtils.isEmpty(text)) {
8727                 event.getText().add(text);
8728             }
8729         }
8730     }
8731
8732     /**
8733      * @return true if the user has explicitly allowed accessibility services
8734      * to speak passwords.
8735      */
8736     private boolean shouldSpeakPasswordsForAccessibility() {
8737         return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8738                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8739                 UserHandle.USER_CURRENT_OR_SELF) == 1);
8740     }
8741
8742     @Override
8743     public CharSequence getAccessibilityClassName() {
8744         return TextView.class.getName();
8745     }
8746
8747     @Override
8748     public void onProvideStructure(ViewStructure structure) {
8749         super.onProvideStructure(structure);
8750         final boolean isPassword = hasPasswordTransformationMethod()
8751                 || isPasswordInputType(getInputType());
8752         if (!isPassword) {
8753             structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8754
8755             // Extract style information that applies to the TextView as a whole.
8756             int style = 0;
8757             int typefaceStyle = getTypefaceStyle();
8758             if ((typefaceStyle & Typeface.BOLD) != 0) {
8759                 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8760             }
8761             if ((typefaceStyle & Typeface.ITALIC) != 0) {
8762                 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
8763             }
8764
8765             // Global styles can also be set via TextView.setPaintFlags().
8766             int paintFlags = mTextPaint.getFlags();
8767             if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
8768                 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8769             }
8770             if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
8771                 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
8772             }
8773             if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
8774                 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
8775             }
8776
8777             // TextView does not have its own text background color. A background is either part
8778             // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
8779             structure.setTextStyle(getTextSize(), getCurrentTextColor(),
8780                     AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
8781         }
8782         structure.setHint(getHint());
8783     }
8784
8785     /** @hide */
8786     @Override
8787     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
8788         super.onInitializeAccessibilityEventInternal(event);
8789
8790         final boolean isPassword = hasPasswordTransformationMethod();
8791         event.setPassword(isPassword);
8792
8793         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8794             event.setFromIndex(Selection.getSelectionStart(mText));
8795             event.setToIndex(Selection.getSelectionEnd(mText));
8796             event.setItemCount(mText.length());
8797         }
8798     }
8799
8800     /** @hide */
8801     @Override
8802     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
8803         super.onInitializeAccessibilityNodeInfoInternal(info);
8804
8805         final boolean isPassword = hasPasswordTransformationMethod();
8806         info.setPassword(isPassword);
8807
8808         if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8809             info.setText(getTextForAccessibility());
8810         }
8811
8812         if (mBufferType == BufferType.EDITABLE) {
8813             info.setEditable(true);
8814         }
8815
8816         if (mEditor != null) {
8817             info.setInputType(mEditor.mInputType);
8818
8819             if (mEditor.mError != null) {
8820                 info.setContentInvalid(true);
8821                 info.setError(mEditor.mError);
8822             }
8823         }
8824
8825         if (!TextUtils.isEmpty(mText)) {
8826             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8827             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8828             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8829                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8830                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8831                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8832                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8833         }
8834
8835         if (isFocused()) {
8836             if (canSelectText()) {
8837                 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8838             }
8839             if (canCopy()) {
8840                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8841             }
8842             if (canPaste()) {
8843                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8844             }
8845             if (canCut()) {
8846                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8847             }
8848         }
8849
8850         // Check for known input filter types.
8851         final int numFilters = mFilters.length;
8852         for (int i = 0; i < numFilters; i++) {
8853             final InputFilter filter = mFilters[i];
8854             if (filter instanceof InputFilter.LengthFilter) {
8855                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
8856             }
8857         }
8858
8859         if (!isSingleLine()) {
8860             info.setMultiLine(true);
8861         }
8862     }
8863
8864     /**
8865      * Performs an accessibility action after it has been offered to the
8866      * delegate.
8867      *
8868      * @hide
8869      */
8870     @Override
8871     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
8872         switch (action) {
8873             case AccessibilityNodeInfo.ACTION_CLICK: {
8874                 boolean handled = false;
8875
8876                 // Simulate View.onTouchEvent for an ACTION_UP event.
8877                 if (isClickable() || isLongClickable()) {
8878                     if (isFocusable() && !isFocused()) {
8879                         requestFocus();
8880                     }
8881
8882                     performClick();
8883                     handled = true;
8884                 }
8885
8886                 // Simulate TextView.onTouchEvent for an ACTION_UP event.
8887                 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8888                         && mText instanceof Spannable && mLayout != null
8889                         && (isTextEditable() || isTextSelectable()) && isFocused()) {
8890                     // Show the IME, except when selecting in read-only text.
8891                     final InputMethodManager imm = InputMethodManager.peekInstance();
8892                     viewClicked(imm);
8893                     if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
8894                         handled |= imm.showSoftInput(this, 0);
8895                     }
8896                 }
8897
8898                 return handled;
8899             }
8900             case AccessibilityNodeInfo.ACTION_COPY: {
8901                 if (isFocused() && canCopy()) {
8902                     if (onTextContextMenuItem(ID_COPY)) {
8903                         return true;
8904                     }
8905                 }
8906             } return false;
8907             case AccessibilityNodeInfo.ACTION_PASTE: {
8908                 if (isFocused() && canPaste()) {
8909                     if (onTextContextMenuItem(ID_PASTE)) {
8910                         return true;
8911                     }
8912                 }
8913             } return false;
8914             case AccessibilityNodeInfo.ACTION_CUT: {
8915                 if (isFocused() && canCut()) {
8916                     if (onTextContextMenuItem(ID_CUT)) {
8917                         return true;
8918                     }
8919                 }
8920             } return false;
8921             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8922                 if (isFocused() && canSelectText()) {
8923                     ensureIterableTextForAccessibilitySelectable();
8924                     CharSequence text = getIterableTextForAccessibility();
8925                     if (text == null) {
8926                         return false;
8927                     }
8928                     final int start = (arguments != null) ? arguments.getInt(
8929                             AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8930                     final int end = (arguments != null) ? arguments.getInt(
8931                             AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8932                     if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8933                         // No arguments clears the selection.
8934                         if (start == end && end == -1) {
8935                             Selection.removeSelection((Spannable) text);
8936                             return true;
8937                         }
8938                         if (start >= 0 && start <= end && end <= text.length()) {
8939                             Selection.setSelection((Spannable) text, start, end);
8940                             // Make sure selection mode is engaged.
8941                             if (mEditor != null) {
8942                                 mEditor.startSelectionActionMode();
8943                             }
8944                             return true;
8945                         }
8946                     }
8947                 }
8948             } return false;
8949             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
8950             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
8951                 ensureIterableTextForAccessibilitySelectable();
8952                 return super.performAccessibilityActionInternal(action, arguments);
8953             }
8954             default: {
8955                 return super.performAccessibilityActionInternal(action, arguments);
8956             }
8957         }
8958     }
8959
8960     /** @hide */
8961     @Override
8962     public void sendAccessibilityEventInternal(int eventType) {
8963         // Do not send scroll events since first they are not interesting for
8964         // accessibility and second such events a generated too frequently.
8965         // For details see the implementation of bringTextIntoView().
8966         if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8967             return;
8968         }
8969         super.sendAccessibilityEventInternal(eventType);
8970     }
8971
8972     /**
8973      * Gets the text reported for accessibility purposes.
8974      *
8975      * @return The accessibility text.
8976      *
8977      * @hide
8978      */
8979     public CharSequence getTextForAccessibility() {
8980         CharSequence text = getText();
8981         if (TextUtils.isEmpty(text)) {
8982             text = getHint();
8983         }
8984         return text;
8985     }
8986
8987     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8988             int fromIndex, int removedCount, int addedCount) {
8989         AccessibilityEvent event =
8990             AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8991         event.setFromIndex(fromIndex);
8992         event.setRemovedCount(removedCount);
8993         event.setAddedCount(addedCount);
8994         event.setBeforeText(beforeText);
8995         sendAccessibilityEventUnchecked(event);
8996     }
8997
8998     /**
8999      * Returns whether this text view is a current input method target.  The
9000      * default implementation just checks with {@link InputMethodManager}.
9001      */
9002     public boolean isInputMethodTarget() {
9003         InputMethodManager imm = InputMethodManager.peekInstance();
9004         return imm != null && imm.isActive(this);
9005     }
9006
9007     static final int ID_SELECT_ALL = android.R.id.selectAll;
9008     static final int ID_UNDO = android.R.id.undo;
9009     static final int ID_REDO = android.R.id.redo;
9010     static final int ID_CUT = android.R.id.cut;
9011     static final int ID_COPY = android.R.id.copy;
9012     static final int ID_PASTE = android.R.id.paste;
9013     static final int ID_SHARE = android.R.id.shareText;
9014     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9015     static final int ID_REPLACE = android.R.id.replaceText;
9016
9017     /**
9018      * Called when a context menu option for the text view is selected.  Currently
9019      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9020      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9021      *
9022      * @return true if the context menu item action was performed.
9023      */
9024     public boolean onTextContextMenuItem(int id) {
9025         int min = 0;
9026         int max = mText.length();
9027
9028         if (isFocused()) {
9029             final int selStart = getSelectionStart();
9030             final int selEnd = getSelectionEnd();
9031
9032             min = Math.max(0, Math.min(selStart, selEnd));
9033             max = Math.max(0, Math.max(selStart, selEnd));
9034         }
9035
9036         switch (id) {
9037             case ID_SELECT_ALL:
9038                 // This starts an action mode if triggered from another action mode. Text is
9039                 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9040                 // true even if text is empty.
9041                 boolean shouldRestartActionMode =
9042                         mEditor != null && mEditor.mTextActionMode != null;
9043                 stopTextActionMode();
9044                 selectAllText();
9045                 if (shouldRestartActionMode) {
9046                     mEditor.startSelectionActionMode();
9047                 }
9048                 return true;
9049
9050             case ID_UNDO:
9051                 if (mEditor != null) {
9052                     mEditor.undo();
9053                 }
9054                 return true;  // Returns true even if nothing was undone.
9055
9056             case ID_REDO:
9057                 if (mEditor != null) {
9058                     mEditor.redo();
9059                 }
9060                 return true;  // Returns true even if nothing was undone.
9061
9062             case ID_PASTE:
9063                 paste(min, max, true /* withFormatting */);
9064                 return true;
9065
9066             case ID_PASTE_AS_PLAIN_TEXT:
9067                 paste(min, max, false /* withFormatting */);
9068                 return true;
9069
9070             case ID_CUT:
9071                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9072                 deleteText_internal(min, max);
9073                 stopTextActionMode();
9074                 return true;
9075
9076             case ID_COPY:
9077                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9078                 stopTextActionMode();
9079                 return true;
9080
9081             case ID_REPLACE:
9082                 if (mEditor != null) {
9083                     mEditor.replace();
9084                 }
9085                 return true;
9086
9087             case ID_SHARE:
9088                 shareSelectedText();
9089                 return true;
9090         }
9091         return false;
9092     }
9093
9094     CharSequence getTransformedText(int start, int end) {
9095         return removeSuggestionSpans(mTransformed.subSequence(start, end));
9096     }
9097
9098     @Override
9099     public boolean performLongClick() {
9100         boolean handled = false;
9101
9102         if (super.performLongClick()) {
9103             handled = true;
9104         }
9105
9106         if (mEditor != null) {
9107             handled |= mEditor.performLongClick(handled);
9108         }
9109
9110         if (handled) {
9111             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9112             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9113         }
9114
9115         return handled;
9116     }
9117
9118     @Override
9119     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9120         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9121         if (mEditor != null) {
9122             mEditor.onScrollChanged();
9123         }
9124     }
9125
9126     /**
9127      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9128      * by the IME or by the spell checker as the user types. This is done by adding
9129      * {@link SuggestionSpan}s to the text.
9130      *
9131      * When suggestions are enabled (default), this list of suggestions will be displayed when the
9132      * user asks for them on these parts of the text. This value depends on the inputType of this
9133      * TextView.
9134      *
9135      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9136      *
9137      * In addition, the type variation must be one of
9138      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9139      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9140      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9141      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9142      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9143      *
9144      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9145      *
9146      * @return true if the suggestions popup window is enabled, based on the inputType.
9147      */
9148     public boolean isSuggestionsEnabled() {
9149         if (mEditor == null) return false;
9150         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9151             return false;
9152         }
9153         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9154
9155         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9156         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9157                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9158                 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9159                 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9160                 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9161     }
9162
9163     /**
9164      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9165      * selection is initiated in this View.
9166      *
9167      * The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9168      * Paste, Replace and Share actions, depending on what this View supports.
9169      *
9170      * A custom implementation can add new entries in the default menu in its
9171      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
9172      * default actions can also be removed from the menu using
9173      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9174      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9175      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9176      *
9177      * Returning false from
9178      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
9179      * the action mode from being started.
9180      *
9181      * Action click events should be handled by the custom implementation of
9182      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
9183      *
9184      * Note that text selection mode is not started when a TextView receives focus and the
9185      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9186      * that case, to allow for quick replacement.
9187      */
9188     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9189         createEditorIfNeeded();
9190         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9191     }
9192
9193     /**
9194      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9195      *
9196      * @return The current custom selection callback.
9197      */
9198     public ActionMode.Callback getCustomSelectionActionModeCallback() {
9199         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9200     }
9201
9202     /**
9203      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9204      * insertion is initiated in this View.
9205      * The standard implementation populates the menu with a subset of Select All,
9206      * Paste and Replace actions, depending on what this View supports.
9207      *
9208      * <p>A custom implementation can add new entries in the default menu in its
9209      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9210      * android.view.Menu)} method. The default actions can also be removed from the menu using
9211      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9212      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9213      *
9214      * <p>Returning false from
9215      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9216      * android.view.Menu)} will prevent the action mode from being started.</p>
9217      *
9218      * <p>Action click events should be handled by the custom implementation of
9219      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9220      * android.view.MenuItem)}.</p>
9221      *
9222      * <p>Note that text insertion mode is not started when a TextView receives focus and the
9223      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9224      */
9225     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9226         createEditorIfNeeded();
9227         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9228     }
9229
9230     /**
9231      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9232      *
9233      * @return The current custom insertion callback.
9234      */
9235     public ActionMode.Callback getCustomInsertionActionModeCallback() {
9236         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9237     }
9238
9239     /**
9240      * @hide
9241      */
9242     protected void stopTextActionMode() {
9243         if (mEditor != null) {
9244             mEditor.stopTextActionMode();
9245         }
9246     }
9247
9248     boolean canUndo() {
9249         return mEditor != null && mEditor.canUndo();
9250     }
9251
9252     boolean canRedo() {
9253         return mEditor != null && mEditor.canRedo();
9254     }
9255
9256     boolean canCut() {
9257         if (hasPasswordTransformationMethod()) {
9258             return false;
9259         }
9260
9261         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9262                 mEditor.mKeyListener != null) {
9263             return true;
9264         }
9265
9266         return false;
9267     }
9268
9269     boolean canCopy() {
9270         if (hasPasswordTransformationMethod()) {
9271             return false;
9272         }
9273
9274         if (mText.length() > 0 && hasSelection() && mEditor != null) {
9275             return true;
9276         }
9277
9278         return false;
9279     }
9280
9281     boolean canShare() {
9282         return canCopy();
9283     }
9284
9285     boolean canPaste() {
9286         return (mText instanceof Editable &&
9287                 mEditor != null && mEditor.mKeyListener != null &&
9288                 getSelectionStart() >= 0 &&
9289                 getSelectionEnd() >= 0 &&
9290                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9291                 hasPrimaryClip());
9292     }
9293
9294     boolean canProcessText() {
9295         if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9296                 || hasPasswordTransformationMethod()) {
9297             return false;
9298         }
9299
9300         if (mText.length() > 0 && hasSelection() && mEditor != null) {
9301             return true;
9302         }
9303
9304         return false;
9305     }
9306
9307     boolean canSelectAllText() {
9308         return canSelectText() && !hasPasswordTransformationMethod()
9309                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9310     }
9311
9312     boolean selectAllText() {
9313         // Need to hide insert point cursor controller before settings selection, otherwise insert
9314         // point cursor controller obtains cursor update event and update cursor with cancelling
9315         // selection.
9316         if (mEditor != null) {
9317             mEditor.hideInsertionPointCursorController();
9318         }
9319         final int length = mText.length();
9320         Selection.setSelection((Spannable) mText, 0, length);
9321         return length > 0;
9322     }
9323
9324     void replaceSelectionWithText(CharSequence text) {
9325         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9326     }
9327
9328     /**
9329      * Paste clipboard content between min and max positions.
9330      */
9331     private void paste(int min, int max, boolean withFormatting) {
9332         ClipboardManager clipboard =
9333             (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9334         ClipData clip = clipboard.getPrimaryClip();
9335         if (clip != null) {
9336             boolean didFirst = false;
9337             for (int i=0; i<clip.getItemCount(); i++) {
9338                 final CharSequence paste;
9339                 if (withFormatting) {
9340                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
9341                 } else {
9342                     // Get an item as text and remove all spans by toString().
9343                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9344                     paste = (text instanceof Spanned) ? text.toString() : text;
9345                 }
9346                 if (paste != null) {
9347                     if (!didFirst) {
9348                         Selection.setSelection((Spannable) mText, max);
9349                         ((Editable) mText).replace(min, max, paste);
9350                         didFirst = true;
9351                     } else {
9352                         ((Editable) mText).insert(getSelectionEnd(), "\n");
9353                         ((Editable) mText).insert(getSelectionEnd(), paste);
9354                     }
9355                 }
9356             }
9357             stopTextActionMode();
9358             sLastCutCopyOrTextChangedTime = 0;
9359         }
9360     }
9361
9362     private void shareSelectedText() {
9363         String selectedText = getSelectedText();
9364         if (selectedText != null && !selectedText.isEmpty()) {
9365             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9366             sharingIntent.setType("text/plain");
9367             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9368             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9369             getContext().startActivity(Intent.createChooser(sharingIntent, null));
9370             stopTextActionMode();
9371         }
9372     }
9373
9374     private void setPrimaryClip(ClipData clip) {
9375         ClipboardManager clipboard = (ClipboardManager) getContext().
9376                 getSystemService(Context.CLIPBOARD_SERVICE);
9377         clipboard.setPrimaryClip(clip);
9378         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9379     }
9380
9381     /**
9382      * Get the character offset closest to the specified absolute position. A typical use case is to
9383      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9384      *
9385      * @param x The horizontal absolute position of a point on screen
9386      * @param y The vertical absolute position of a point on screen
9387      * @return the character offset for the character whose position is closest to the specified
9388      *  position. Returns -1 if there is no layout.
9389      */
9390     public int getOffsetForPosition(float x, float y) {
9391         if (getLayout() == null) return -1;
9392         final int line = getLineAtCoordinate(y);
9393         final int offset = getOffsetAtCoordinate(line, x);
9394         return offset;
9395     }
9396
9397     float convertToLocalHorizontalCoordinate(float x) {
9398         x -= getTotalPaddingLeft();
9399         // Clamp the position to inside of the view.
9400         x = Math.max(0.0f, x);
9401         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9402         x += getScrollX();
9403         return x;
9404     }
9405
9406     int getLineAtCoordinate(float y) {
9407         y -= getTotalPaddingTop();
9408         // Clamp the position to inside of the view.
9409         y = Math.max(0.0f, y);
9410         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9411         y += getScrollY();
9412         return getLayout().getLineForVertical((int) y);
9413     }
9414
9415     int getOffsetAtCoordinate(int line, float x) {
9416         x = convertToLocalHorizontalCoordinate(x);
9417         return getLayout().getOffsetForHorizontal(line, x);
9418     }
9419
9420     @Override
9421     public boolean onDragEvent(DragEvent event) {
9422         switch (event.getAction()) {
9423             case DragEvent.ACTION_DRAG_STARTED:
9424                 return mEditor != null && mEditor.hasInsertionController();
9425
9426             case DragEvent.ACTION_DRAG_ENTERED:
9427                 TextView.this.requestFocus();
9428                 return true;
9429
9430             case DragEvent.ACTION_DRAG_LOCATION:
9431                 final int offset = getOffsetForPosition(event.getX(), event.getY());
9432                 Selection.setSelection((Spannable)mText, offset);
9433                 return true;
9434
9435             case DragEvent.ACTION_DROP:
9436                 if (mEditor != null) mEditor.onDrop(event);
9437                 return true;
9438
9439             case DragEvent.ACTION_DRAG_ENDED:
9440             case DragEvent.ACTION_DRAG_EXITED:
9441             default:
9442                 return true;
9443         }
9444     }
9445
9446     boolean isInBatchEditMode() {
9447         if (mEditor == null) return false;
9448         final Editor.InputMethodState ims = mEditor.mInputMethodState;
9449         if (ims != null) {
9450             return ims.mBatchEditNesting > 0;
9451         }
9452         return mEditor.mInBatchEditControllers;
9453     }
9454
9455     @Override
9456     public void onRtlPropertiesChanged(int layoutDirection) {
9457         super.onRtlPropertiesChanged(layoutDirection);
9458
9459         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9460         if (mTextDir != newTextDir) {
9461             mTextDir = newTextDir;
9462             if (mLayout != null) {
9463                 checkForRelayout();
9464             }
9465         }
9466     }
9467
9468     TextDirectionHeuristic getTextDirectionHeuristic() {
9469         if (hasPasswordTransformationMethod()) {
9470             // passwords fields should be LTR
9471             return TextDirectionHeuristics.LTR;
9472         }
9473
9474         // Always need to resolve layout direction first
9475         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9476
9477         // Now, we can select the heuristic
9478         switch (getTextDirection()) {
9479             default:
9480             case TEXT_DIRECTION_FIRST_STRONG:
9481                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9482                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
9483             case TEXT_DIRECTION_ANY_RTL:
9484                 return TextDirectionHeuristics.ANYRTL_LTR;
9485             case TEXT_DIRECTION_LTR:
9486                 return TextDirectionHeuristics.LTR;
9487             case TEXT_DIRECTION_RTL:
9488                 return TextDirectionHeuristics.RTL;
9489             case TEXT_DIRECTION_LOCALE:
9490                 return TextDirectionHeuristics.LOCALE;
9491             case TEXT_DIRECTION_FIRST_STRONG_LTR:
9492                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9493             case TEXT_DIRECTION_FIRST_STRONG_RTL:
9494                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9495         }
9496     }
9497
9498     /**
9499      * @hide
9500      */
9501     @Override
9502     public void onResolveDrawables(int layoutDirection) {
9503         // No need to resolve twice
9504         if (mLastLayoutDirection == layoutDirection) {
9505             return;
9506         }
9507         mLastLayoutDirection = layoutDirection;
9508
9509         // Resolve drawables
9510         if (mDrawables != null) {
9511             mDrawables.resolveWithLayoutDirection(layoutDirection);
9512         }
9513     }
9514
9515     /**
9516      * @hide
9517      */
9518     protected void resetResolvedDrawables() {
9519         super.resetResolvedDrawables();
9520         mLastLayoutDirection = -1;
9521     }
9522
9523     /**
9524      * @hide
9525      */
9526     protected void viewClicked(InputMethodManager imm) {
9527         if (imm != null) {
9528             imm.viewClicked(this);
9529         }
9530     }
9531
9532     /**
9533      * Deletes the range of text [start, end[.
9534      * @hide
9535      */
9536     protected void deleteText_internal(int start, int end) {
9537         ((Editable) mText).delete(start, end);
9538     }
9539
9540     /**
9541      * Replaces the range of text [start, end[ by replacement text
9542      * @hide
9543      */
9544     protected void replaceText_internal(int start, int end, CharSequence text) {
9545         ((Editable) mText).replace(start, end, text);
9546     }
9547
9548     /**
9549      * Sets a span on the specified range of text
9550      * @hide
9551      */
9552     protected void setSpan_internal(Object span, int start, int end, int flags) {
9553         ((Editable) mText).setSpan(span, start, end, flags);
9554     }
9555
9556     /**
9557      * Moves the cursor to the specified offset position in text
9558      * @hide
9559      */
9560     protected void setCursorPosition_internal(int start, int end) {
9561         Selection.setSelection(((Editable) mText), start, end);
9562     }
9563
9564     /**
9565      * An Editor should be created as soon as any of the editable-specific fields (grouped
9566      * inside the Editor object) is assigned to a non-default value.
9567      * This method will create the Editor if needed.
9568      *
9569      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9570      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9571      * Editor for backward compatibility, as soon as one of these fields is assigned.
9572      *
9573      * Also note that for performance reasons, the mEditor is created when needed, but not
9574      * reset when no more edit-specific fields are needed.
9575      */
9576     private void createEditorIfNeeded() {
9577         if (mEditor == null) {
9578             mEditor = new Editor(this);
9579         }
9580     }
9581
9582     /**
9583      * @hide
9584      */
9585     @Override
9586     public CharSequence getIterableTextForAccessibility() {
9587         return mText;
9588     }
9589
9590     private void ensureIterableTextForAccessibilitySelectable() {
9591         if (!(mText instanceof Spannable)) {
9592             setText(mText, BufferType.SPANNABLE);
9593         }
9594     }
9595
9596     /**
9597      * @hide
9598      */
9599     @Override
9600     public TextSegmentIterator getIteratorForGranularity(int granularity) {
9601         switch (granularity) {
9602             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9603                 Spannable text = (Spannable) getIterableTextForAccessibility();
9604                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9605                     AccessibilityIterators.LineTextSegmentIterator iterator =
9606                         AccessibilityIterators.LineTextSegmentIterator.getInstance();
9607                     iterator.initialize(text, getLayout());
9608                     return iterator;
9609                 }
9610             } break;
9611             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9612                 Spannable text = (Spannable) getIterableTextForAccessibility();
9613                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9614                     AccessibilityIterators.PageTextSegmentIterator iterator =
9615                         AccessibilityIterators.PageTextSegmentIterator.getInstance();
9616                     iterator.initialize(this);
9617                     return iterator;
9618                 }
9619             } break;
9620         }
9621         return super.getIteratorForGranularity(granularity);
9622     }
9623
9624     /**
9625      * @hide
9626      */
9627     @Override
9628     public int getAccessibilitySelectionStart() {
9629         return getSelectionStart();
9630     }
9631
9632     /**
9633      * @hide
9634      */
9635     public boolean isAccessibilitySelectionExtendable() {
9636         return true;
9637     }
9638
9639     /**
9640      * @hide
9641      */
9642     @Override
9643     public int getAccessibilitySelectionEnd() {
9644         return getSelectionEnd();
9645     }
9646
9647     /**
9648      * @hide
9649      */
9650     @Override
9651     public void setAccessibilitySelection(int start, int end) {
9652         if (getAccessibilitySelectionStart() == start
9653                 && getAccessibilitySelectionEnd() == end) {
9654             return;
9655         }
9656         // Hide all selection controllers used for adjusting selection
9657         // since we are doing so explicitlty by other means and these
9658         // controllers interact with how selection behaves.
9659         if (mEditor != null) {
9660             mEditor.hideCursorAndSpanControllers();
9661         }
9662         CharSequence text = getIterableTextForAccessibility();
9663         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
9664             Selection.setSelection((Spannable) text, start, end);
9665         } else {
9666             Selection.removeSelection((Spannable) text);
9667         }
9668     }
9669
9670     /** @hide */
9671     @Override
9672     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
9673         super.encodeProperties(stream);
9674
9675         TruncateAt ellipsize = getEllipsize();
9676         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
9677         stream.addProperty("text:textSize", getTextSize());
9678         stream.addProperty("text:scaledTextSize", getScaledTextSize());
9679         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
9680         stream.addProperty("text:selectionStart", getSelectionStart());
9681         stream.addProperty("text:selectionEnd", getSelectionEnd());
9682         stream.addProperty("text:curTextColor", mCurTextColor);
9683         stream.addProperty("text:text", mText == null ? null : mText.toString());
9684         stream.addProperty("text:gravity", mGravity);
9685     }
9686
9687     /**
9688      * User interface state that is stored by TextView for implementing
9689      * {@link View#onSaveInstanceState}.
9690      */
9691     public static class SavedState extends BaseSavedState {
9692         int selStart;
9693         int selEnd;
9694         CharSequence text;
9695         boolean frozenWithFocus;
9696         CharSequence error;
9697         ParcelableParcel editorState;  // Optional state from Editor.
9698
9699         SavedState(Parcelable superState) {
9700             super(superState);
9701         }
9702
9703         @Override
9704         public void writeToParcel(Parcel out, int flags) {
9705             super.writeToParcel(out, flags);
9706             out.writeInt(selStart);
9707             out.writeInt(selEnd);
9708             out.writeInt(frozenWithFocus ? 1 : 0);
9709             TextUtils.writeToParcel(text, out, flags);
9710
9711             if (error == null) {
9712                 out.writeInt(0);
9713             } else {
9714                 out.writeInt(1);
9715                 TextUtils.writeToParcel(error, out, flags);
9716             }
9717
9718             if (editorState == null) {
9719                 out.writeInt(0);
9720             } else {
9721                 out.writeInt(1);
9722                 editorState.writeToParcel(out, flags);
9723             }
9724         }
9725
9726         @Override
9727         public String toString() {
9728             String str = "TextView.SavedState{"
9729                     + Integer.toHexString(System.identityHashCode(this))
9730                     + " start=" + selStart + " end=" + selEnd;
9731             if (text != null) {
9732                 str += " text=" + text;
9733             }
9734             return str + "}";
9735         }
9736
9737         @SuppressWarnings("hiding")
9738         public static final Parcelable.Creator<SavedState> CREATOR
9739                 = new Parcelable.Creator<SavedState>() {
9740             public SavedState createFromParcel(Parcel in) {
9741                 return new SavedState(in);
9742             }
9743
9744             public SavedState[] newArray(int size) {
9745                 return new SavedState[size];
9746             }
9747         };
9748
9749         private SavedState(Parcel in) {
9750             super(in);
9751             selStart = in.readInt();
9752             selEnd = in.readInt();
9753             frozenWithFocus = (in.readInt() != 0);
9754             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9755
9756             if (in.readInt() != 0) {
9757                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9758             }
9759
9760             if (in.readInt() != 0) {
9761                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
9762             }
9763         }
9764     }
9765
9766     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
9767         private char[] mChars;
9768         private int mStart, mLength;
9769
9770         public CharWrapper(char[] chars, int start, int len) {
9771             mChars = chars;
9772             mStart = start;
9773             mLength = len;
9774         }
9775
9776         /* package */ void set(char[] chars, int start, int len) {
9777             mChars = chars;
9778             mStart = start;
9779             mLength = len;
9780         }
9781
9782         public int length() {
9783             return mLength;
9784         }
9785
9786         public char charAt(int off) {
9787             return mChars[off + mStart];
9788         }
9789
9790         @Override
9791         public String toString() {
9792             return new String(mChars, mStart, mLength);
9793         }
9794
9795         public CharSequence subSequence(int start, int end) {
9796             if (start < 0 || end < 0 || start > mLength || end > mLength) {
9797                 throw new IndexOutOfBoundsException(start + ", " + end);
9798             }
9799
9800             return new String(mChars, start + mStart, end - start);
9801         }
9802
9803         public void getChars(int start, int end, char[] buf, int off) {
9804             if (start < 0 || end < 0 || start > mLength || end > mLength) {
9805                 throw new IndexOutOfBoundsException(start + ", " + end);
9806             }
9807
9808             System.arraycopy(mChars, start + mStart, buf, off, end - start);
9809         }
9810
9811         public void drawText(Canvas c, int start, int end,
9812                              float x, float y, Paint p) {
9813             c.drawText(mChars, start + mStart, end - start, x, y, p);
9814         }
9815
9816         public void drawTextRun(Canvas c, int start, int end,
9817                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
9818             int count = end - start;
9819             int contextCount = contextEnd - contextStart;
9820             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9821                     contextCount, x, y, isRtl, p);
9822         }
9823
9824         public float measureText(int start, int end, Paint p) {
9825             return p.measureText(mChars, start + mStart, end - start);
9826         }
9827
9828         public int getTextWidths(int start, int end, float[] widths, Paint p) {
9829             return p.getTextWidths(mChars, start + mStart, end - start, widths);
9830         }
9831
9832         public float getTextRunAdvances(int start, int end, int contextStart,
9833                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
9834                 Paint p) {
9835             int count = end - start;
9836             int contextCount = contextEnd - contextStart;
9837             return p.getTextRunAdvances(mChars, start + mStart, count,
9838                     contextStart + mStart, contextCount, isRtl, advances,
9839                     advancesIndex);
9840         }
9841
9842         public int getTextRunCursor(int contextStart, int contextEnd, int dir,
9843                 int offset, int cursorOpt, Paint p) {
9844             int contextCount = contextEnd - contextStart;
9845             return p.getTextRunCursor(mChars, contextStart + mStart,
9846                     contextCount, dir, offset + mStart, cursorOpt);
9847         }
9848     }
9849
9850     private static final class Marquee {
9851         // TODO: Add an option to configure this
9852         private static final float MARQUEE_DELTA_MAX = 0.07f;
9853         private static final int MARQUEE_DELAY = 1200;
9854         private static final int MARQUEE_DP_PER_SECOND = 30;
9855
9856         private static final byte MARQUEE_STOPPED = 0x0;
9857         private static final byte MARQUEE_STARTING = 0x1;
9858         private static final byte MARQUEE_RUNNING = 0x2;
9859
9860         private final WeakReference<TextView> mView;
9861         private final Choreographer mChoreographer;
9862
9863         private byte mStatus = MARQUEE_STOPPED;
9864         private final float mPixelsPerSecond;
9865         private float mMaxScroll;
9866         private float mMaxFadeScroll;
9867         private float mGhostStart;
9868         private float mGhostOffset;
9869         private float mFadeStop;
9870         private int mRepeatLimit;
9871
9872         private float mScroll;
9873         private long mLastAnimationMs;
9874
9875         Marquee(TextView v) {
9876             final float density = v.getContext().getResources().getDisplayMetrics().density;
9877             mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
9878             mView = new WeakReference<TextView>(v);
9879             mChoreographer = Choreographer.getInstance();
9880         }
9881
9882         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
9883             @Override
9884             public void doFrame(long frameTimeNanos) {
9885                 tick();
9886             }
9887         };
9888
9889         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
9890             @Override
9891             public void doFrame(long frameTimeNanos) {
9892                 mStatus = MARQUEE_RUNNING;
9893                 mLastAnimationMs = mChoreographer.getFrameTime();
9894                 tick();
9895             }
9896         };
9897
9898         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
9899             @Override
9900             public void doFrame(long frameTimeNanos) {
9901                 if (mStatus == MARQUEE_RUNNING) {
9902                     if (mRepeatLimit >= 0) {
9903                         mRepeatLimit--;
9904                     }
9905                     start(mRepeatLimit);
9906                 }
9907             }
9908         };
9909
9910         void tick() {
9911             if (mStatus != MARQUEE_RUNNING) {
9912                 return;
9913             }
9914
9915             mChoreographer.removeFrameCallback(mTickCallback);
9916
9917             final TextView textView = mView.get();
9918             if (textView != null && (textView.isFocused() || textView.isSelected())) {
9919                 long currentMs = mChoreographer.getFrameTime();
9920                 long deltaMs = currentMs - mLastAnimationMs;
9921                 mLastAnimationMs = currentMs;
9922                 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
9923                 mScroll += deltaPx;
9924                 if (mScroll > mMaxScroll) {
9925                     mScroll = mMaxScroll;
9926                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
9927                 } else {
9928                     mChoreographer.postFrameCallback(mTickCallback);
9929                 }
9930                 textView.invalidate();
9931             }
9932         }
9933
9934         void stop() {
9935             mStatus = MARQUEE_STOPPED;
9936             mChoreographer.removeFrameCallback(mStartCallback);
9937             mChoreographer.removeFrameCallback(mRestartCallback);
9938             mChoreographer.removeFrameCallback(mTickCallback);
9939             resetScroll();
9940         }
9941
9942         private void resetScroll() {
9943             mScroll = 0.0f;
9944             final TextView textView = mView.get();
9945             if (textView != null) textView.invalidate();
9946         }
9947
9948         void start(int repeatLimit) {
9949             if (repeatLimit == 0) {
9950                 stop();
9951                 return;
9952             }
9953             mRepeatLimit = repeatLimit;
9954             final TextView textView = mView.get();
9955             if (textView != null && textView.mLayout != null) {
9956                 mStatus = MARQUEE_STARTING;
9957                 mScroll = 0.0f;
9958                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9959                         textView.getCompoundPaddingRight();
9960                 final float lineWidth = textView.mLayout.getLineWidth(0);
9961                 final float gap = textWidth / 3.0f;
9962                 mGhostStart = lineWidth - textWidth + gap;
9963                 mMaxScroll = mGhostStart + textWidth;
9964                 mGhostOffset = lineWidth + gap;
9965                 mFadeStop = lineWidth + textWidth / 6.0f;
9966                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9967
9968                 textView.invalidate();
9969                 mChoreographer.postFrameCallback(mStartCallback);
9970             }
9971         }
9972
9973         float getGhostOffset() {
9974             return mGhostOffset;
9975         }
9976
9977         float getScroll() {
9978             return mScroll;
9979         }
9980
9981         float getMaxFadeScroll() {
9982             return mMaxFadeScroll;
9983         }
9984
9985         boolean shouldDrawLeftFade() {
9986             return mScroll <= mFadeStop;
9987         }
9988
9989         boolean shouldDrawGhost() {
9990             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9991         }
9992
9993         boolean isRunning() {
9994             return mStatus == MARQUEE_RUNNING;
9995         }
9996
9997         boolean isStopped() {
9998             return mStatus == MARQUEE_STOPPED;
9999         }
10000     }
10001
10002     private class ChangeWatcher implements TextWatcher, SpanWatcher {
10003
10004         private CharSequence mBeforeText;
10005
10006         public void beforeTextChanged(CharSequence buffer, int start,
10007                                       int before, int after) {
10008             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10009                     + " before=" + before + " after=" + after + ": " + buffer);
10010
10011             if (AccessibilityManager.getInstance(mContext).isEnabled()
10012                     && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10013                             || shouldSpeakPasswordsForAccessibility())) {
10014                 mBeforeText = buffer.toString();
10015             }
10016
10017             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10018         }
10019
10020         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10021             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10022                     + " before=" + before + " after=" + after + ": " + buffer);
10023             TextView.this.handleTextChanged(buffer, start, before, after);
10024
10025             if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10026                     (isFocused() || isSelected() && isShown())) {
10027                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10028                 mBeforeText = null;
10029             }
10030         }
10031
10032         public void afterTextChanged(Editable buffer) {
10033             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10034             TextView.this.sendAfterTextChanged(buffer);
10035
10036             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10037                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10038             }
10039         }
10040
10041         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10042             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10043                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10044             TextView.this.spanChange(buf, what, s, st, e, en);
10045         }
10046
10047         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10048             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10049                     + " what=" + what + ": " + buf);
10050             TextView.this.spanChange(buf, what, -1, s, -1, e);
10051         }
10052
10053         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10054             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10055                     + " what=" + what + ": " + buf);
10056             TextView.this.spanChange(buf, what, s, -1, e, -1);
10057         }
10058     }
10059 }