OSDN Git Service

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