OSDN Git Service

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