OSDN Git Service

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