OSDN Git Service

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