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