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