OSDN Git Service

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