OSDN Git Service

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