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