TextView.java revision d24b8183b93e781080b2c16c487e60d51c12da31
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.content.Context;
20import android.content.Intent;
21import android.content.res.ColorStateList;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.content.res.XmlResourceParser;
25import android.graphics.Canvas;
26import android.graphics.Paint;
27import android.graphics.Path;
28import android.graphics.Rect;
29import android.graphics.RectF;
30import android.graphics.Typeface;
31import android.graphics.drawable.Drawable;
32import android.os.Bundle;
33import android.os.Handler;
34import android.os.Parcel;
35import android.os.Parcelable;
36import android.os.SystemClock;
37import android.os.Message;
38import android.text.BoringLayout;
39import android.text.DynamicLayout;
40import android.text.Editable;
41import android.text.GetChars;
42import android.text.GraphicsOperations;
43import android.text.ClipboardManager;
44import android.text.InputFilter;
45import android.text.Layout;
46import android.text.ParcelableSpan;
47import android.text.Selection;
48import android.text.SpanWatcher;
49import android.text.Spannable;
50import android.text.Spanned;
51import android.text.SpannedString;
52import android.text.SpannableString;
53import android.text.StaticLayout;
54import android.text.TextPaint;
55import android.text.TextUtils;
56import android.text.TextWatcher;
57import android.text.method.DateKeyListener;
58import android.text.method.DateTimeKeyListener;
59import android.text.method.DialerKeyListener;
60import android.text.method.DigitsKeyListener;
61import android.text.method.KeyListener;
62import android.text.method.LinkMovementMethod;
63import android.text.method.MetaKeyKeyListener;
64import android.text.method.MovementMethod;
65import android.text.method.TimeKeyListener;
66
67import android.text.method.PasswordTransformationMethod;
68import android.text.method.SingleLineTransformationMethod;
69import android.text.method.TextKeyListener;
70import android.text.method.TransformationMethod;
71import android.text.style.ParagraphStyle;
72import android.text.style.URLSpan;
73import android.text.style.UpdateAppearance;
74import android.text.util.Linkify;
75import android.util.AttributeSet;
76import android.util.Log;
77import android.util.FloatMath;
78import android.util.TypedValue;
79import android.view.ContextMenu;
80import android.view.Gravity;
81import android.view.KeyEvent;
82import android.view.LayoutInflater;
83import android.view.MenuItem;
84import android.view.MotionEvent;
85import android.view.View;
86import android.view.ViewDebug;
87import android.view.ViewTreeObserver;
88import android.view.ViewGroup.LayoutParams;
89import android.view.animation.AnimationUtils;
90import android.view.inputmethod.CompletionInfo;
91import android.view.inputmethod.ExtractedText;
92import android.view.inputmethod.ExtractedTextRequest;
93import android.view.inputmethod.InputConnection;
94import android.view.inputmethod.InputMethodManager;
95import android.view.inputmethod.EditorInfo;
96import android.widget.RemoteViews.RemoteView;
97
98import java.io.IOException;
99import java.lang.ref.WeakReference;
100import java.util.ArrayList;
101
102import com.android.internal.util.FastMath;
103import com.android.internal.widget.EditableInputConnection;
104
105import org.xmlpull.v1.XmlPullParserException;
106
107/**
108 * Displays text to the user and optionally allows them to edit it.  A TextView
109 * is a complete text editor, however the basic class is configured to not
110 * allow editing; see {@link EditText} for a subclass that configures the text
111 * view for editing.
112 *
113 * <p>
114 * <b>XML attributes</b>
115 * <p>
116 * See {@link android.R.styleable#TextView TextView Attributes},
117 * {@link android.R.styleable#View View Attributes}
118 *
119 * @attr ref android.R.styleable#TextView_text
120 * @attr ref android.R.styleable#TextView_bufferType
121 * @attr ref android.R.styleable#TextView_hint
122 * @attr ref android.R.styleable#TextView_textColor
123 * @attr ref android.R.styleable#TextView_textColorHighlight
124 * @attr ref android.R.styleable#TextView_textColorHint
125 * @attr ref android.R.styleable#TextView_textSize
126 * @attr ref android.R.styleable#TextView_textScaleX
127 * @attr ref android.R.styleable#TextView_typeface
128 * @attr ref android.R.styleable#TextView_textStyle
129 * @attr ref android.R.styleable#TextView_cursorVisible
130 * @attr ref android.R.styleable#TextView_maxLines
131 * @attr ref android.R.styleable#TextView_maxHeight
132 * @attr ref android.R.styleable#TextView_lines
133 * @attr ref android.R.styleable#TextView_height
134 * @attr ref android.R.styleable#TextView_minLines
135 * @attr ref android.R.styleable#TextView_minHeight
136 * @attr ref android.R.styleable#TextView_maxEms
137 * @attr ref android.R.styleable#TextView_maxWidth
138 * @attr ref android.R.styleable#TextView_ems
139 * @attr ref android.R.styleable#TextView_width
140 * @attr ref android.R.styleable#TextView_minEms
141 * @attr ref android.R.styleable#TextView_minWidth
142 * @attr ref android.R.styleable#TextView_gravity
143 * @attr ref android.R.styleable#TextView_scrollHorizontally
144 * @attr ref android.R.styleable#TextView_password
145 * @attr ref android.R.styleable#TextView_singleLine
146 * @attr ref android.R.styleable#TextView_selectAllOnFocus
147 * @attr ref android.R.styleable#TextView_includeFontPadding
148 * @attr ref android.R.styleable#TextView_maxLength
149 * @attr ref android.R.styleable#TextView_shadowColor
150 * @attr ref android.R.styleable#TextView_shadowDx
151 * @attr ref android.R.styleable#TextView_shadowDy
152 * @attr ref android.R.styleable#TextView_shadowRadius
153 * @attr ref android.R.styleable#TextView_autoLink
154 * @attr ref android.R.styleable#TextView_linksClickable
155 * @attr ref android.R.styleable#TextView_numeric
156 * @attr ref android.R.styleable#TextView_digits
157 * @attr ref android.R.styleable#TextView_phoneNumber
158 * @attr ref android.R.styleable#TextView_inputMethod
159 * @attr ref android.R.styleable#TextView_capitalize
160 * @attr ref android.R.styleable#TextView_autoText
161 * @attr ref android.R.styleable#TextView_editable
162 * @attr ref android.R.styleable#TextView_drawableTop
163 * @attr ref android.R.styleable#TextView_drawableBottom
164 * @attr ref android.R.styleable#TextView_drawableRight
165 * @attr ref android.R.styleable#TextView_drawableLeft
166 * @attr ref android.R.styleable#TextView_lineSpacingExtra
167 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
168 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
169 */
170@RemoteView
171public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
172    static final String TAG = "TextView";
173    static final boolean DEBUG_EXTRACT = false;
174
175    private static int PRIORITY = 100;
176
177    private ColorStateList mTextColor;
178    private int mCurTextColor;
179    private ColorStateList mHintTextColor;
180    private ColorStateList mLinkTextColor;
181    private int mCurHintTextColor;
182    private boolean mFreezesText;
183    private boolean mFrozenWithFocus;
184
185    private boolean mEatTouchRelease = false;
186    private boolean mScrolled = false;
187
188    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
189    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
190
191    private float mShadowRadius, mShadowDx, mShadowDy;
192
193    private static final int PREDRAW_NOT_REGISTERED = 0;
194    private static final int PREDRAW_PENDING = 1;
195    private static final int PREDRAW_DONE = 2;
196    private int mPreDrawState = PREDRAW_NOT_REGISTERED;
197
198    private TextUtils.TruncateAt mEllipsize = null;
199
200    // Enum for the "typeface" XML parameter.
201    // TODO: How can we get this from the XML instead of hardcoding it here?
202    private static final int SANS = 1;
203    private static final int SERIF = 2;
204    private static final int MONOSPACE = 3;
205
206    // Bitfield for the "numeric" XML parameter.
207    // TODO: How can we get this from the XML instead of hardcoding it here?
208    private static final int SIGNED = 2;
209    private static final int DECIMAL = 4;
210
211    class Drawables {
212        final Rect mCompoundRect = new Rect();
213        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
214        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
215        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
216        int mDrawablePadding;
217    };
218    private Drawables mDrawables;
219
220    private CharSequence mError;
221    private boolean mErrorWasChanged;
222    private PopupWindow mPopup;
223    /**
224     * This flag is set if the TextView tries to display an error before it
225     * is attached to the window (so its position is still unknown).
226     * It causes the error to be shown later, when onAttachedToWindow()
227     * is called.
228     */
229    private boolean mShowErrorAfterAttach;
230
231    private CharWrapper mCharWrapper = null;
232
233    private boolean mSelectionMoved = false;
234
235    private Marquee mMarquee;
236    private boolean mRestartMarquee;
237
238    private int mMarqueeRepeatLimit = 3;
239
240    class InputContentType {
241        String privateContentType;
242        Bundle extras;
243    }
244    InputContentType mInputContentType;
245
246    class InputMethodState {
247        Rect mCursorRectInWindow = new Rect();
248        RectF mTmpRectF = new RectF();
249        float[] mTmpOffset = new float[2];
250        ExtractedTextRequest mExtracting;
251        final ExtractedText mTmpExtracted = new ExtractedText();
252        int mBatchEditNesting;
253        boolean mCursorChanged;
254        boolean mContentChanged;
255        int mChangedStart, mChangedEnd, mChangedDelta;
256    }
257    InputMethodState mInputMethodState;
258
259    /*
260     * Kick-start the font cache for the zygote process (to pay the cost of
261     * initializing freetype for our default font only once).
262     */
263    static {
264        Paint p = new Paint();
265        p.setAntiAlias(true);
266        // We don't care about the result, just the side-effect of measuring.
267        p.measureText("H");
268    }
269
270    public TextView(Context context) {
271        this(context, null);
272    }
273
274    public TextView(Context context,
275                    AttributeSet attrs) {
276        this(context, attrs, com.android.internal.R.attr.textViewStyle);
277    }
278
279    public TextView(Context context,
280                    AttributeSet attrs,
281                    int defStyle) {
282        super(context, attrs, defStyle);
283        mText = "";
284
285        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
286        // If we get the paint from the skin, we should set it to left, since
287        // the layout always wants it to be left.
288        // mTextPaint.setTextAlign(Paint.Align.LEFT);
289
290        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
291
292        mMovement = getDefaultMovementMethod();
293        mTransformation = null;
294
295        TypedArray a =
296            context.obtainStyledAttributes(
297                attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
298
299        int textColorHighlight = 0;
300        ColorStateList textColor = null;
301        ColorStateList textColorHint = null;
302        ColorStateList textColorLink = null;
303        int textSize = 15;
304        int typefaceIndex = -1;
305        int styleIndex = -1;
306
307        /*
308         * Look the appearance up without checking first if it exists because
309         * almost every TextView has one and it greatly simplifies the logic
310         * to be able to parse the appearance first and then let specific tags
311         * for this View override it.
312         */
313        TypedArray appearance = null;
314        int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
315        if (ap != -1) {
316            appearance = context.obtainStyledAttributes(ap,
317                                com.android.internal.R.styleable.
318                                TextAppearance);
319        }
320        if (appearance != null) {
321            int n = appearance.getIndexCount();
322            for (int i = 0; i < n; i++) {
323                int attr = appearance.getIndex(i);
324
325                switch (attr) {
326                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
327                    textColorHighlight = appearance.getColor(attr, textColorHighlight);
328                    break;
329
330                case com.android.internal.R.styleable.TextAppearance_textColor:
331                    textColor = appearance.getColorStateList(attr);
332                    break;
333
334                case com.android.internal.R.styleable.TextAppearance_textColorHint:
335                    textColorHint = appearance.getColorStateList(attr);
336                    break;
337
338                case com.android.internal.R.styleable.TextAppearance_textColorLink:
339                    textColorLink = appearance.getColorStateList(attr);
340                    break;
341
342                case com.android.internal.R.styleable.TextAppearance_textSize:
343                    textSize = appearance.getDimensionPixelSize(attr, textSize);
344                    break;
345
346                case com.android.internal.R.styleable.TextAppearance_typeface:
347                    typefaceIndex = appearance.getInt(attr, -1);
348                    break;
349
350                case com.android.internal.R.styleable.TextAppearance_textStyle:
351                    styleIndex = appearance.getInt(attr, -1);
352                    break;
353                }
354            }
355
356            appearance.recycle();
357        }
358
359        boolean editable = getDefaultEditable();
360        CharSequence inputMethod = null;
361        int numeric = 0;
362        CharSequence digits = null;
363        boolean phone = false;
364        boolean autotext = false;
365        int autocap = -1;
366        int buffertype = 0;
367        boolean selectallonfocus = false;
368        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
369            drawableBottom = null;
370        int drawablePadding = 0;
371        int ellipsize = -1;
372        boolean singleLine = false;
373        int maxlength = -1;
374        CharSequence text = "";
375        int shadowcolor = 0;
376        float dx = 0, dy = 0, r = 0;
377        boolean password = false;
378        int contentType = EditorInfo.TYPE_NULL;
379
380        int n = a.getIndexCount();
381        for (int i = 0; i < n; i++) {
382            int attr = a.getIndex(i);
383
384            switch (attr) {
385            case com.android.internal.R.styleable.TextView_editable:
386                editable = a.getBoolean(attr, editable);
387                break;
388
389            case com.android.internal.R.styleable.TextView_inputMethod:
390                inputMethod = a.getText(attr);
391                break;
392
393            case com.android.internal.R.styleable.TextView_numeric:
394                numeric = a.getInt(attr, numeric);
395                break;
396
397            case com.android.internal.R.styleable.TextView_digits:
398                digits = a.getText(attr);
399                break;
400
401            case com.android.internal.R.styleable.TextView_phoneNumber:
402                phone = a.getBoolean(attr, phone);
403                break;
404
405            case com.android.internal.R.styleable.TextView_autoText:
406                autotext = a.getBoolean(attr, autotext);
407                break;
408
409            case com.android.internal.R.styleable.TextView_capitalize:
410                autocap = a.getInt(attr, autocap);
411                break;
412
413            case com.android.internal.R.styleable.TextView_bufferType:
414                buffertype = a.getInt(attr, buffertype);
415                break;
416
417            case com.android.internal.R.styleable.TextView_selectAllOnFocus:
418                selectallonfocus = a.getBoolean(attr, selectallonfocus);
419                break;
420
421            case com.android.internal.R.styleable.TextView_autoLink:
422                mAutoLinkMask = a.getInt(attr, 0);
423                break;
424
425            case com.android.internal.R.styleable.TextView_linksClickable:
426                mLinksClickable = a.getBoolean(attr, true);
427                break;
428
429            case com.android.internal.R.styleable.TextView_drawableLeft:
430                drawableLeft = a.getDrawable(attr);
431                break;
432
433            case com.android.internal.R.styleable.TextView_drawableTop:
434                drawableTop = a.getDrawable(attr);
435                break;
436
437            case com.android.internal.R.styleable.TextView_drawableRight:
438                drawableRight = a.getDrawable(attr);
439                break;
440
441            case com.android.internal.R.styleable.TextView_drawableBottom:
442                drawableBottom = a.getDrawable(attr);
443                break;
444
445            case com.android.internal.R.styleable.TextView_drawablePadding:
446                drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
447                break;
448
449            case com.android.internal.R.styleable.TextView_maxLines:
450                setMaxLines(a.getInt(attr, -1));
451                break;
452
453            case com.android.internal.R.styleable.TextView_maxHeight:
454                setMaxHeight(a.getDimensionPixelSize(attr, -1));
455                break;
456
457            case com.android.internal.R.styleable.TextView_lines:
458                setLines(a.getInt(attr, -1));
459                break;
460
461            case com.android.internal.R.styleable.TextView_height:
462                setHeight(a.getDimensionPixelSize(attr, -1));
463                break;
464
465            case com.android.internal.R.styleable.TextView_minLines:
466                setMinLines(a.getInt(attr, -1));
467                break;
468
469            case com.android.internal.R.styleable.TextView_minHeight:
470                setMinHeight(a.getDimensionPixelSize(attr, -1));
471                break;
472
473            case com.android.internal.R.styleable.TextView_maxEms:
474                setMaxEms(a.getInt(attr, -1));
475                break;
476
477            case com.android.internal.R.styleable.TextView_maxWidth:
478                setMaxWidth(a.getDimensionPixelSize(attr, -1));
479                break;
480
481            case com.android.internal.R.styleable.TextView_ems:
482                setEms(a.getInt(attr, -1));
483                break;
484
485            case com.android.internal.R.styleable.TextView_width:
486                setWidth(a.getDimensionPixelSize(attr, -1));
487                break;
488
489            case com.android.internal.R.styleable.TextView_minEms:
490                setMinEms(a.getInt(attr, -1));
491                break;
492
493            case com.android.internal.R.styleable.TextView_minWidth:
494                setMinWidth(a.getDimensionPixelSize(attr, -1));
495                break;
496
497            case com.android.internal.R.styleable.TextView_gravity:
498                setGravity(a.getInt(attr, -1));
499                break;
500
501            case com.android.internal.R.styleable.TextView_hint:
502                setHint(a.getText(attr));
503                break;
504
505            case com.android.internal.R.styleable.TextView_text:
506                text = a.getText(attr);
507                break;
508
509            case com.android.internal.R.styleable.TextView_scrollHorizontally:
510                if (a.getBoolean(attr, false)) {
511                    setHorizontallyScrolling(true);
512                }
513                break;
514
515            case com.android.internal.R.styleable.TextView_singleLine:
516                singleLine = a.getBoolean(attr, singleLine);
517                break;
518
519            case com.android.internal.R.styleable.TextView_ellipsize:
520                ellipsize = a.getInt(attr, ellipsize);
521                break;
522
523            case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
524                setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
525                break;
526
527            case com.android.internal.R.styleable.TextView_includeFontPadding:
528                if (!a.getBoolean(attr, true)) {
529                    setIncludeFontPadding(false);
530                }
531                break;
532
533            case com.android.internal.R.styleable.TextView_cursorVisible:
534                if (!a.getBoolean(attr, true)) {
535                    setCursorVisible(false);
536                }
537                break;
538
539            case com.android.internal.R.styleable.TextView_maxLength:
540                maxlength = a.getInt(attr, -1);
541                break;
542
543            case com.android.internal.R.styleable.TextView_textScaleX:
544                setTextScaleX(a.getFloat(attr, 1.0f));
545                break;
546
547            case com.android.internal.R.styleable.TextView_freezesText:
548                mFreezesText = a.getBoolean(attr, false);
549                break;
550
551            case com.android.internal.R.styleable.TextView_shadowColor:
552                shadowcolor = a.getInt(attr, 0);
553                break;
554
555            case com.android.internal.R.styleable.TextView_shadowDx:
556                dx = a.getFloat(attr, 0);
557                break;
558
559            case com.android.internal.R.styleable.TextView_shadowDy:
560                dy = a.getFloat(attr, 0);
561                break;
562
563            case com.android.internal.R.styleable.TextView_shadowRadius:
564                r = a.getFloat(attr, 0);
565                break;
566
567            case com.android.internal.R.styleable.TextView_enabled:
568                setEnabled(a.getBoolean(attr, isEnabled()));
569                break;
570
571            case com.android.internal.R.styleable.TextView_textColorHighlight:
572                textColorHighlight = a.getColor(attr, textColorHighlight);
573                break;
574
575            case com.android.internal.R.styleable.TextView_textColor:
576                textColor = a.getColorStateList(attr);
577                break;
578
579            case com.android.internal.R.styleable.TextView_textColorHint:
580                textColorHint = a.getColorStateList(attr);
581                break;
582
583            case com.android.internal.R.styleable.TextView_textColorLink:
584                textColorLink = a.getColorStateList(attr);
585                break;
586
587            case com.android.internal.R.styleable.TextView_textSize:
588                textSize = a.getDimensionPixelSize(attr, textSize);
589                break;
590
591            case com.android.internal.R.styleable.TextView_typeface:
592                typefaceIndex = a.getInt(attr, typefaceIndex);
593                break;
594
595            case com.android.internal.R.styleable.TextView_textStyle:
596                styleIndex = a.getInt(attr, styleIndex);
597                break;
598
599            case com.android.internal.R.styleable.TextView_password:
600                password = a.getBoolean(attr, password);
601                break;
602
603            case com.android.internal.R.styleable.TextView_lineSpacingExtra:
604                mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
605                break;
606
607            case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
608                mSpacingMult = a.getFloat(attr, mSpacingMult);
609                break;
610
611            case com.android.internal.R.styleable.TextView_inputType:
612                contentType = a.getInt(attr, mInputType);
613                break;
614
615            case com.android.internal.R.styleable.TextView_editorPrivateContentType:
616                setPrivateContentType(a.getString(attr));
617                break;
618
619            case com.android.internal.R.styleable.TextView_editorExtras:
620                try {
621                    setInputExtras(a.getResourceId(attr, 0));
622                } catch (XmlPullParserException e) {
623                    Log.w("TextView", "Failure reading input extras", e);
624                } catch (IOException e) {
625                    Log.w("TextView", "Failure reading input extras", e);
626                }
627                break;
628            }
629        }
630        a.recycle();
631
632        BufferType bufferType = BufferType.EDITABLE;
633
634        if ((contentType&(EditorInfo.TYPE_MASK_CLASS
635                |EditorInfo.TYPE_MASK_VARIATION))
636                == (EditorInfo.TYPE_CLASS_TEXT
637                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
638            password = true;
639        }
640
641        if (inputMethod != null) {
642            Class c;
643
644            try {
645                c = Class.forName(inputMethod.toString());
646            } catch (ClassNotFoundException ex) {
647                throw new RuntimeException(ex);
648            }
649
650            try {
651                mInput = (KeyListener) c.newInstance();
652            } catch (InstantiationException ex) {
653                throw new RuntimeException(ex);
654            } catch (IllegalAccessException ex) {
655                throw new RuntimeException(ex);
656            }
657            try {
658                mInputType = contentType != EditorInfo.TYPE_NULL
659                        ? contentType
660                        : mInput.getInputType();
661            } catch (IncompatibleClassChangeError e) {
662                mInputType = EditorInfo.TYPE_CLASS_TEXT;
663            }
664        } else if (digits != null) {
665            mInput = DigitsKeyListener.getInstance(digits.toString());
666            mInputType = contentType;
667        } else if (contentType != EditorInfo.TYPE_NULL) {
668            setInputType(contentType, true);
669            singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS
670                            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
671                    (EditorInfo.TYPE_CLASS_TEXT
672                            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
673        } else if (phone) {
674            mInput = DialerKeyListener.getInstance();
675            contentType = EditorInfo.TYPE_CLASS_PHONE;
676        } else if (numeric != 0) {
677            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
678                                                   (numeric & DECIMAL) != 0);
679            contentType = EditorInfo.TYPE_CLASS_NUMBER;
680            if ((numeric & SIGNED) != 0) {
681                contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
682            }
683            if ((numeric & DECIMAL) != 0) {
684                contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
685            }
686            mInputType = contentType;
687        } else if (autotext || autocap != -1) {
688            TextKeyListener.Capitalize cap;
689
690            contentType = EditorInfo.TYPE_CLASS_TEXT;
691            if (!singleLine) {
692                contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
693            }
694
695            switch (autocap) {
696            case 1:
697                cap = TextKeyListener.Capitalize.SENTENCES;
698                contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
699                break;
700
701            case 2:
702                cap = TextKeyListener.Capitalize.WORDS;
703                contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
704                break;
705
706            case 3:
707                cap = TextKeyListener.Capitalize.CHARACTERS;
708                contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
709                break;
710
711            default:
712                cap = TextKeyListener.Capitalize.NONE;
713                break;
714            }
715
716            mInput = TextKeyListener.getInstance(autotext, cap);
717            mInputType = contentType;
718        } else if (editable) {
719            mInput = TextKeyListener.getInstance();
720            mInputType = EditorInfo.TYPE_CLASS_TEXT;
721        } else {
722            mInput = null;
723
724            switch (buffertype) {
725                case 0:
726                    bufferType = BufferType.NORMAL;
727                    break;
728                case 1:
729                    bufferType = BufferType.SPANNABLE;
730                    break;
731                case 2:
732                    bufferType = BufferType.EDITABLE;
733                    break;
734            }
735        }
736
737        if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS)
738                == EditorInfo.TYPE_CLASS_TEXT) {
739            mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
740                | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
741        }
742
743        if (selectallonfocus) {
744            mSelectAllOnFocus = true;
745
746            if (bufferType == BufferType.NORMAL)
747                bufferType = BufferType.SPANNABLE;
748        }
749
750        setCompoundDrawablesWithIntrinsicBounds(
751            drawableLeft, drawableTop, drawableRight, drawableBottom);
752        setCompoundDrawablePadding(drawablePadding);
753
754        if (singleLine) {
755            setSingleLine();
756
757            if (mInput == null && ellipsize < 0) {
758                ellipsize = 3; // END
759            }
760        }
761
762        switch (ellipsize) {
763            case 1:
764                setEllipsize(TextUtils.TruncateAt.START);
765                break;
766            case 2:
767                setEllipsize(TextUtils.TruncateAt.MIDDLE);
768                break;
769            case 3:
770                setEllipsize(TextUtils.TruncateAt.END);
771                break;
772            case 4:
773                setHorizontalFadingEdgeEnabled(true);
774                setEllipsize(TextUtils.TruncateAt.MARQUEE);
775                break;
776        }
777
778        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
779        setHintTextColor(textColorHint);
780        setLinkTextColor(textColorLink);
781        if (textColorHighlight != 0) {
782            setHighlightColor(textColorHighlight);
783        }
784        setRawTextSize(textSize);
785
786        if (password) {
787            setTransformationMethod(PasswordTransformationMethod.getInstance());
788            typefaceIndex = MONOSPACE;
789        }
790
791        setTypefaceByIndex(typefaceIndex, styleIndex);
792
793        if (shadowcolor != 0) {
794            setShadowLayer(r, dx, dy, shadowcolor);
795        }
796
797        if (maxlength >= 0) {
798            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
799        } else {
800            setFilters(NO_FILTERS);
801        }
802
803        setText(text, bufferType);
804
805        /*
806         * Views are not normally focusable unless specified to be.
807         * However, TextViews that have input or movement methods *are*
808         * focusable by default.
809         */
810        a = context.obtainStyledAttributes(attrs,
811                                           com.android.internal.R.styleable.View,
812                                           defStyle, 0);
813
814        boolean focusable = mMovement != null || mInput != null;
815        boolean clickable = focusable;
816        boolean longClickable = focusable;
817
818        n = a.getIndexCount();
819        for (int i = 0; i < n; i++) {
820            int attr = a.getIndex(i);
821
822            switch (attr) {
823            case com.android.internal.R.styleable.View_focusable:
824                focusable = a.getBoolean(attr, focusable);
825                break;
826
827            case com.android.internal.R.styleable.View_clickable:
828                clickable = a.getBoolean(attr, clickable);
829                break;
830
831            case com.android.internal.R.styleable.View_longClickable:
832                longClickable = a.getBoolean(attr, longClickable);
833                break;
834            }
835        }
836        a.recycle();
837
838        setFocusable(focusable);
839        setClickable(clickable);
840        setLongClickable(longClickable);
841    }
842
843    private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
844        Typeface tf = null;
845        switch (typefaceIndex) {
846            case SANS:
847                tf = Typeface.SANS_SERIF;
848                break;
849
850            case SERIF:
851                tf = Typeface.SERIF;
852                break;
853
854            case MONOSPACE:
855                tf = Typeface.MONOSPACE;
856                break;
857        }
858
859        setTypeface(tf, styleIndex);
860    }
861
862    /**
863     * Sets the typeface and style in which the text should be displayed,
864     * and turns on the fake bold and italic bits in the Paint if the
865     * Typeface that you provided does not have all the bits in the
866     * style that you specified.
867     *
868     * @attr ref android.R.styleable#TextView_typeface
869     * @attr ref android.R.styleable#TextView_textStyle
870     */
871    public void setTypeface(Typeface tf, int style) {
872        if (style > 0) {
873            if (tf == null) {
874                tf = Typeface.defaultFromStyle(style);
875            } else {
876                tf = Typeface.create(tf, style);
877            }
878
879            setTypeface(tf);
880            // now compute what (if any) algorithmic styling is needed
881            int typefaceStyle = tf != null ? tf.getStyle() : 0;
882            int need = style & ~typefaceStyle;
883            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
884            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
885        } else {
886            mTextPaint.setFakeBoldText(false);
887            mTextPaint.setTextSkewX(0);
888            setTypeface(tf);
889        }
890    }
891
892    /**
893     * Subclasses override this to specify that they have a KeyListener
894     * by default even if not specifically called for in the XML options.
895     */
896    protected boolean getDefaultEditable() {
897        return false;
898    }
899
900    /**
901     * Subclasses override this to specify a default movement method.
902     */
903    protected MovementMethod getDefaultMovementMethod() {
904        return null;
905    }
906
907    /**
908     * Return the text the TextView is displaying. If setText() was called with
909     * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
910     * the return value from this method to Spannable or Editable, respectively.
911     *
912     * Note: The content of the return value should not be modified. If you want
913     * a modifiable one, you should make your own copy first.
914     */
915    @ViewDebug.CapturedViewProperty
916    public CharSequence getText() {
917        return mText;
918    }
919
920    /**
921     * Returns the length, in characters, of the text managed by this TextView
922     */
923    public int length() {
924        return mText.length();
925    }
926
927    /**
928     * Return the text the TextView is displaying as an Editable object.  If
929     * the text is not editable, null is returned.
930     *
931     * @see #getText
932     */
933    public Editable getEditableText() {
934        return (mText instanceof Editable) ? (Editable)mText : null;
935    }
936
937    /**
938     * @return the height of one standard line in pixels.  Note that markup
939     * within the text can cause individual lines to be taller or shorter
940     * than this height, and the layout may contain additional first-
941     * or last-line padding.
942     */
943    public int getLineHeight() {
944        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
945                          + mSpacingAdd);
946    }
947
948    /**
949     * @return the Layout that is currently being used to display the text.
950     * This can be null if the text or width has recently changes.
951     */
952    public final Layout getLayout() {
953        return mLayout;
954    }
955
956    /**
957     * @return the current key listener for this TextView.
958     * This will frequently be null for non-EditText TextViews.
959     */
960    public final KeyListener getKeyListener() {
961        return mInput;
962    }
963
964    /**
965     * Sets the key listener to be used with this TextView.  This can be null
966     * to disallow user input.  Note that this method has significant and
967     * subtle interactions with soft keyboards and other input method:
968     * see {@link KeyListener#getInputType() KeyListener.getContentType()}
969     * for important details.  Calling this method will replace the current
970     * content type of the text view with the content type returned by the
971     * key listener.
972     * <p>
973     * Be warned that if you want a TextView with a key listener or movement
974     * method not to be focusable, or if you want a TextView without a
975     * key listener or movement method to be focusable, you must call
976     * {@link #setFocusable} again after calling this to get the focusability
977     * back the way you want it.
978     *
979     * @attr ref android.R.styleable#TextView_numeric
980     * @attr ref android.R.styleable#TextView_digits
981     * @attr ref android.R.styleable#TextView_phoneNumber
982     * @attr ref android.R.styleable#TextView_inputMethod
983     * @attr ref android.R.styleable#TextView_capitalize
984     * @attr ref android.R.styleable#TextView_autoText
985     */
986    public void setKeyListener(KeyListener input) {
987        setKeyListenerOnly(input);
988        fixFocusableAndClickableSettings();
989
990        if (input != null) {
991            try {
992                mInputType = mInput.getInputType();
993            } catch (IncompatibleClassChangeError e) {
994                mInputType = EditorInfo.TYPE_CLASS_TEXT;
995            }
996            if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
997                    == EditorInfo.TYPE_CLASS_TEXT) {
998                if (mSingleLine) {
999                    mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
1000                } else {
1001                    mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
1002                }
1003            }
1004        } else {
1005            mInputType = EditorInfo.TYPE_NULL;
1006        }
1007
1008        InputMethodManager imm = InputMethodManager.peekInstance();
1009        if (imm != null) imm.restartInput(this);
1010    }
1011
1012    private void setKeyListenerOnly(KeyListener input) {
1013        mInput = input;
1014        if (mInput != null && !(mText instanceof Editable))
1015            setText(mText);
1016
1017        setFilters((Editable) mText, mFilters);
1018    }
1019
1020    /**
1021     * @return the movement method being used for this TextView.
1022     * This will frequently be null for non-EditText TextViews.
1023     */
1024    public final MovementMethod getMovementMethod() {
1025        return mMovement;
1026    }
1027
1028    /**
1029     * Sets the movement method (arrow key handler) to be used for
1030     * this TextView.  This can be null to disallow using the arrow keys
1031     * to move the cursor or scroll the view.
1032     * <p>
1033     * Be warned that if you want a TextView with a key listener or movement
1034     * method not to be focusable, or if you want a TextView without a
1035     * key listener or movement method to be focusable, you must call
1036     * {@link #setFocusable} again after calling this to get the focusability
1037     * back the way you want it.
1038     */
1039    public final void setMovementMethod(MovementMethod movement) {
1040        mMovement = movement;
1041
1042        if (mMovement != null && !(mText instanceof Spannable))
1043            setText(mText);
1044
1045        fixFocusableAndClickableSettings();
1046    }
1047
1048    private void fixFocusableAndClickableSettings() {
1049        if ((mMovement != null) || mInput != null) {
1050            setFocusable(true);
1051            setClickable(true);
1052            setLongClickable(true);
1053        } else {
1054            setFocusable(false);
1055            setClickable(false);
1056            setLongClickable(false);
1057        }
1058    }
1059
1060    /**
1061     * @return the current transformation method for this TextView.
1062     * This will frequently be null except for single-line and password
1063     * fields.
1064     */
1065    public final TransformationMethod getTransformationMethod() {
1066        return mTransformation;
1067    }
1068
1069    /**
1070     * Sets the transformation that is applied to the text that this
1071     * TextView is displaying.
1072     *
1073     * @attr ref android.R.styleable#TextView_password
1074     * @attr ref android.R.styleable#TextView_singleLine
1075     */
1076    public final void setTransformationMethod(TransformationMethod method) {
1077        if (mTransformation != null) {
1078            if (mText instanceof Spannable) {
1079                ((Spannable) mText).removeSpan(mTransformation);
1080            }
1081        }
1082
1083        mTransformation = method;
1084
1085        setText(mText);
1086    }
1087
1088    /**
1089     * Returns the top padding of the view, plus space for the top
1090     * Drawable if any.
1091     */
1092    public int getCompoundPaddingTop() {
1093        final Drawables dr = mDrawables;
1094        if (dr == null || dr.mDrawableTop == null) {
1095            return mPaddingTop;
1096        } else {
1097            return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1098        }
1099    }
1100
1101    /**
1102     * Returns the bottom padding of the view, plus space for the bottom
1103     * Drawable if any.
1104     */
1105    public int getCompoundPaddingBottom() {
1106        final Drawables dr = mDrawables;
1107        if (dr == null || dr.mDrawableBottom == null) {
1108            return mPaddingBottom;
1109        } else {
1110            return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1111        }
1112    }
1113
1114    /**
1115     * Returns the left padding of the view, plus space for the left
1116     * Drawable if any.
1117     */
1118    public int getCompoundPaddingLeft() {
1119        final Drawables dr = mDrawables;
1120        if (dr == null || dr.mDrawableLeft == null) {
1121            return mPaddingLeft;
1122        } else {
1123            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1124        }
1125    }
1126
1127    /**
1128     * Returns the right padding of the view, plus space for the right
1129     * Drawable if any.
1130     */
1131    public int getCompoundPaddingRight() {
1132        final Drawables dr = mDrawables;
1133        if (dr == null || dr.mDrawableRight == null) {
1134            return mPaddingRight;
1135        } else {
1136            return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1137        }
1138    }
1139
1140    /**
1141     * Returns the extended top padding of the view, including both the
1142     * top Drawable if any and any extra space to keep more than maxLines
1143     * of text from showing.  It is only valid to call this after measuring.
1144     */
1145    public int getExtendedPaddingTop() {
1146        if (mMaxMode != LINES) {
1147            return getCompoundPaddingTop();
1148        }
1149
1150        if (mLayout.getLineCount() <= mMaximum) {
1151            return getCompoundPaddingTop();
1152        }
1153
1154        int top = getCompoundPaddingTop();
1155        int bottom = getCompoundPaddingBottom();
1156        int viewht = getHeight() - top - bottom;
1157        int layoutht = mLayout.getLineTop(mMaximum);
1158
1159        if (layoutht >= viewht) {
1160            return top;
1161        }
1162
1163        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1164        if (gravity == Gravity.TOP) {
1165            return top;
1166        } else if (gravity == Gravity.BOTTOM) {
1167            return top + viewht - layoutht;
1168        } else { // (gravity == Gravity.CENTER_VERTICAL)
1169            return top + (viewht - layoutht) / 2;
1170        }
1171    }
1172
1173    /**
1174     * Returns the extended bottom padding of the view, including both the
1175     * bottom Drawable if any and any extra space to keep more than maxLines
1176     * of text from showing.  It is only valid to call this after measuring.
1177     */
1178    public int getExtendedPaddingBottom() {
1179        if (mMaxMode != LINES) {
1180            return getCompoundPaddingBottom();
1181        }
1182
1183        if (mLayout.getLineCount() <= mMaximum) {
1184            return getCompoundPaddingBottom();
1185        }
1186
1187        int top = getCompoundPaddingTop();
1188        int bottom = getCompoundPaddingBottom();
1189        int viewht = getHeight() - top - bottom;
1190        int layoutht = mLayout.getLineTop(mMaximum);
1191
1192        if (layoutht >= viewht) {
1193            return bottom;
1194        }
1195
1196        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1197        if (gravity == Gravity.TOP) {
1198            return bottom + viewht - layoutht;
1199        } else if (gravity == Gravity.BOTTOM) {
1200            return bottom;
1201        } else { // (gravity == Gravity.CENTER_VERTICAL)
1202            return bottom + (viewht - layoutht) / 2;
1203        }
1204    }
1205
1206    /**
1207     * Returns the total left padding of the view, including the left
1208     * Drawable if any.
1209     */
1210    public int getTotalPaddingLeft() {
1211        return getCompoundPaddingLeft();
1212    }
1213
1214    /**
1215     * Returns the total right padding of the view, including the right
1216     * Drawable if any.
1217     */
1218    public int getTotalPaddingRight() {
1219        return getCompoundPaddingRight();
1220    }
1221
1222    /**
1223     * Returns the total top padding of the view, including the top
1224     * Drawable if any, the extra space to keep more than maxLines
1225     * from showing, and the vertical offset for gravity, if any.
1226     */
1227    public int getTotalPaddingTop() {
1228        return getExtendedPaddingTop() + getVerticalOffset(true);
1229    }
1230
1231    /**
1232     * Returns the total bottom padding of the view, including the bottom
1233     * Drawable if any, the extra space to keep more than maxLines
1234     * from showing, and the vertical offset for gravity, if any.
1235     */
1236    public int getTotalPaddingBottom() {
1237        return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1238    }
1239
1240    /**
1241     * Sets the Drawables (if any) to appear to the left of, above,
1242     * to the right of, and below the text.  Use null if you do not
1243     * want a Drawable there.  The Drawables must already have had
1244     * {@link Drawable#setBounds} called.
1245     *
1246     * @attr ref android.R.styleable#TextView_drawableLeft
1247     * @attr ref android.R.styleable#TextView_drawableTop
1248     * @attr ref android.R.styleable#TextView_drawableRight
1249     * @attr ref android.R.styleable#TextView_drawableBottom
1250     */
1251    public void setCompoundDrawables(Drawable left, Drawable top,
1252                                     Drawable right, Drawable bottom) {
1253        Drawables dr = mDrawables;
1254
1255        final boolean drawables = left != null || top != null
1256                || right != null || bottom != null;
1257
1258        if (!drawables) {
1259            // Clearing drawables...  can we free the data structure?
1260            if (dr != null) {
1261                if (dr.mDrawablePadding == 0) {
1262                    mDrawables = null;
1263                } else {
1264                    // We need to retain the last set padding, so just clear
1265                    // out all of the fields in the existing structure.
1266                    dr.mDrawableLeft = null;
1267                    dr.mDrawableTop = null;
1268                    dr.mDrawableRight = null;
1269                    dr.mDrawableBottom = null;
1270                    dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1271                    dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1272                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1273                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1274                }
1275            }
1276        } else {
1277            if (dr == null) {
1278                mDrawables = dr = new Drawables();
1279            }
1280
1281            dr.mDrawableLeft = left;
1282            dr.mDrawableTop = top;
1283            dr.mDrawableRight = right;
1284            dr.mDrawableBottom = bottom;
1285
1286            final Rect compoundRect = dr.mCompoundRect;
1287            int[] state = null;
1288
1289            state = getDrawableState();
1290
1291            if (left != null) {
1292                left.setState(state);
1293                left.copyBounds(compoundRect);
1294                dr.mDrawableSizeLeft = compoundRect.width();
1295                dr.mDrawableHeightLeft = compoundRect.height();
1296            } else {
1297                dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1298            }
1299
1300            if (right != null) {
1301                right.setState(state);
1302                right.copyBounds(compoundRect);
1303                dr.mDrawableSizeRight = compoundRect.width();
1304                dr.mDrawableHeightRight = compoundRect.height();
1305            } else {
1306                dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1307            }
1308
1309            if (top != null) {
1310                top.setState(state);
1311                top.copyBounds(compoundRect);
1312                dr.mDrawableSizeTop = compoundRect.height();
1313                dr.mDrawableWidthTop = compoundRect.width();
1314            } else {
1315                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1316            }
1317
1318            if (bottom != null) {
1319                bottom.setState(state);
1320                bottom.copyBounds(compoundRect);
1321                dr.mDrawableSizeBottom = compoundRect.height();
1322                dr.mDrawableWidthBottom = compoundRect.width();
1323            } else {
1324                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1325            }
1326        }
1327
1328        invalidate();
1329        requestLayout();
1330    }
1331
1332    /**
1333     * Sets the Drawables (if any) to appear to the left of, above,
1334     * to the right of, and below the text.  Use 0 if you do not
1335     * want a Drawable there. The Drawables' bounds will be set to
1336     * their intrinsic bounds.
1337     *
1338     * @param left Resource identifier of the left Drawable.
1339     * @param top Resource identifier of the top Drawable.
1340     * @param right Resource identifier of the right Drawable.
1341     * @param bottom Resource identifier of the bottom Drawable.
1342     *
1343     * @attr ref android.R.styleable#TextView_drawableLeft
1344     * @attr ref android.R.styleable#TextView_drawableTop
1345     * @attr ref android.R.styleable#TextView_drawableRight
1346     * @attr ref android.R.styleable#TextView_drawableBottom
1347     */
1348    public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1349        final Resources resources = getContext().getResources();
1350        setCompoundDrawables(left != 0 ? resources.getDrawable(left) : null,
1351                top != 0 ? resources.getDrawable(top) : null,
1352                right != 0 ? resources.getDrawable(right) : null,
1353                bottom != 0 ? resources.getDrawable(bottom) : null);
1354    }
1355
1356    /**
1357     * Sets the Drawables (if any) to appear to the left of, above,
1358     * to the right of, and below the text.  Use null if you do not
1359     * want a Drawable there. The Drawables' bounds will be set to
1360     * their intrinsic bounds.
1361     *
1362     * @attr ref android.R.styleable#TextView_drawableLeft
1363     * @attr ref android.R.styleable#TextView_drawableTop
1364     * @attr ref android.R.styleable#TextView_drawableRight
1365     * @attr ref android.R.styleable#TextView_drawableBottom
1366     */
1367    public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1368            Drawable right, Drawable bottom) {
1369
1370        if (left != null) {
1371            left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1372        }
1373        if (right != null) {
1374            right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1375        }
1376        if (top != null) {
1377            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1378        }
1379        if (bottom != null) {
1380            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1381        }
1382        setCompoundDrawables(left, top, right, bottom);
1383    }
1384
1385    /**
1386     * Returns drawables for the left, top, right, and bottom borders.
1387     */
1388    public Drawable[] getCompoundDrawables() {
1389        final Drawables dr = mDrawables;
1390        if (dr != null) {
1391            return new Drawable[] {
1392                dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
1393            };
1394        } else {
1395            return new Drawable[] { null, null, null, null };
1396        }
1397    }
1398
1399    /**
1400     * Sets the size of the padding between the compound drawables and
1401     * the text.
1402     *
1403     * @attr ref android.R.styleable#TextView_drawablePadding
1404     */
1405    public void setCompoundDrawablePadding(int pad) {
1406        Drawables dr = mDrawables;
1407        if (pad == 0) {
1408            if (dr != null) {
1409                dr.mDrawablePadding = pad;
1410            }
1411        } else {
1412            if (dr == null) {
1413                mDrawables = dr = new Drawables();
1414            }
1415            dr.mDrawablePadding = pad;
1416        }
1417
1418        invalidate();
1419        requestLayout();
1420    }
1421
1422    /**
1423     * Returns the padding between the compound drawables and the text.
1424     */
1425    public int getCompoundDrawablePadding() {
1426        final Drawables dr = mDrawables;
1427        return dr != null ? dr.mDrawablePadding : 0;
1428    }
1429
1430    @Override
1431    public void setPadding(int left, int top, int right, int bottom) {
1432        if (left != getPaddingLeft() ||
1433            right != getPaddingRight() ||
1434            top != getPaddingTop() ||
1435            bottom != getPaddingBottom()) {
1436            nullLayouts();
1437        }
1438
1439        // the super call will requestLayout()
1440        super.setPadding(left, top, right, bottom);
1441        invalidate();
1442    }
1443
1444    /**
1445     * Gets the autolink mask of the text.  See {@link
1446     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1447     * possible values.
1448     *
1449     * @attr ref android.R.styleable#TextView_autoLink
1450     */
1451    public final int getAutoLinkMask() {
1452        return mAutoLinkMask;
1453    }
1454
1455    /**
1456     * Sets the text color, size, style, hint color, and highlight color
1457     * from the specified TextAppearance resource.
1458     */
1459    public void setTextAppearance(Context context, int resid) {
1460        TypedArray appearance =
1461            context.obtainStyledAttributes(resid,
1462                                           com.android.internal.R.styleable.TextAppearance);
1463
1464        int color;
1465        ColorStateList colors;
1466        int ts;
1467
1468        color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
1469        if (color != 0) {
1470            setHighlightColor(color);
1471        }
1472
1473        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1474                                              TextAppearance_textColor);
1475        if (colors != null) {
1476            setTextColor(colors);
1477        }
1478
1479        ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
1480                                              TextAppearance_textSize, 0);
1481        if (ts != 0) {
1482            setRawTextSize(ts);
1483        }
1484
1485        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1486                                              TextAppearance_textColorHint);
1487        if (colors != null) {
1488            setHintTextColor(colors);
1489        }
1490
1491        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1492                                              TextAppearance_textColorLink);
1493        if (colors != null) {
1494            setLinkTextColor(colors);
1495        }
1496
1497        int typefaceIndex, styleIndex;
1498
1499        typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
1500                                          TextAppearance_typeface, -1);
1501        styleIndex = appearance.getInt(com.android.internal.R.styleable.
1502                                       TextAppearance_textStyle, -1);
1503
1504        setTypefaceByIndex(typefaceIndex, styleIndex);
1505        appearance.recycle();
1506    }
1507
1508    /**
1509     * @return the size (in pixels) of the default text size in this TextView.
1510     */
1511    public float getTextSize() {
1512        return mTextPaint.getTextSize();
1513    }
1514
1515    /**
1516     * Set the default text size to the given value, interpreted as "scaled
1517     * pixel" units.  This size is adjusted based on the current density and
1518     * user font size preference.
1519     *
1520     * @param size The scaled pixel size.
1521     *
1522     * @attr ref android.R.styleable#TextView_textSize
1523     */
1524    public void setTextSize(float size) {
1525        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
1526    }
1527
1528    /**
1529     * Set the default text size to a given unit and value.  See {@link
1530     * TypedValue} for the possible dimension units.
1531     *
1532     * @param unit The desired dimension unit.
1533     * @param size The desired size in the given units.
1534     *
1535     * @attr ref android.R.styleable#TextView_textSize
1536     */
1537    public void setTextSize(int unit, float size) {
1538        Context c = getContext();
1539        Resources r;
1540
1541        if (c == null)
1542            r = Resources.getSystem();
1543        else
1544            r = c.getResources();
1545
1546        setRawTextSize(TypedValue.applyDimension(
1547            unit, size, r.getDisplayMetrics()));
1548    }
1549
1550    private void setRawTextSize(float size) {
1551        if (size != mTextPaint.getTextSize()) {
1552            mTextPaint.setTextSize(size);
1553
1554            if (mLayout != null) {
1555                nullLayouts();
1556                requestLayout();
1557                invalidate();
1558            }
1559        }
1560    }
1561
1562    /**
1563     * @return the extent by which text is currently being stretched
1564     * horizontally.  This will usually be 1.
1565     */
1566    public float getTextScaleX() {
1567        return mTextPaint.getTextScaleX();
1568    }
1569
1570    /**
1571     * Sets the extent by which text should be stretched horizontally.
1572     *
1573     * @attr ref android.R.styleable#TextView_textScaleX
1574     */
1575    public void setTextScaleX(float size) {
1576        if (size != mTextPaint.getTextScaleX()) {
1577            mTextPaint.setTextScaleX(size);
1578
1579            if (mLayout != null) {
1580                nullLayouts();
1581                requestLayout();
1582                invalidate();
1583            }
1584        }
1585    }
1586
1587    /**
1588     * Sets the typeface and style in which the text should be displayed.
1589     * Note that not all Typeface families actually have bold and italic
1590     * variants, so you may need to use
1591     * {@link #setTypeface(Typeface, int)} to get the appearance
1592     * that you actually want.
1593     *
1594     * @attr ref android.R.styleable#TextView_typeface
1595     * @attr ref android.R.styleable#TextView_textStyle
1596     */
1597    public void setTypeface(Typeface tf) {
1598        if (mTextPaint.getTypeface() != tf) {
1599            mTextPaint.setTypeface(tf);
1600
1601            if (mLayout != null) {
1602                nullLayouts();
1603                requestLayout();
1604                invalidate();
1605            }
1606        }
1607    }
1608
1609    /**
1610     * @return the current typeface and style in which the text is being
1611     * displayed.
1612     */
1613    public Typeface getTypeface() {
1614        return mTextPaint.getTypeface();
1615    }
1616
1617    /**
1618     * Sets the text color for all the states (normal, selected,
1619     * focused) to be this color.
1620     *
1621     * @attr ref android.R.styleable#TextView_textColor
1622     */
1623    public void setTextColor(int color) {
1624        mTextColor = ColorStateList.valueOf(color);
1625        updateTextColors();
1626    }
1627
1628    /**
1629     * Sets the text color.
1630     *
1631     * @attr ref android.R.styleable#TextView_textColor
1632     */
1633    public void setTextColor(ColorStateList colors) {
1634        if (colors == null) {
1635            throw new NullPointerException();
1636        }
1637
1638        mTextColor = colors;
1639        updateTextColors();
1640    }
1641
1642    /**
1643     * Return the set of text colors.
1644     *
1645     * @return Returns the set of text colors.
1646     */
1647    public final ColorStateList getTextColors() {
1648        return mTextColor;
1649    }
1650
1651    /**
1652     * <p>Return the current color selected for normal text.</p>
1653     *
1654     * @return Returns the current text color.
1655     */
1656    public final int getCurrentTextColor() {
1657        return mCurTextColor;
1658    }
1659
1660    /**
1661     * Sets the color used to display the selection highlight.
1662     *
1663     * @attr ref android.R.styleable#TextView_textColorHighlight
1664     */
1665    public void setHighlightColor(int color) {
1666        if (mHighlightColor != color) {
1667            mHighlightColor = color;
1668            invalidate();
1669        }
1670    }
1671
1672    /**
1673     * Gives the text a shadow of the specified radius and color, the specified
1674     * distance from its normal position.
1675     *
1676     * @attr ref android.R.styleable#TextView_shadowColor
1677     * @attr ref android.R.styleable#TextView_shadowDx
1678     * @attr ref android.R.styleable#TextView_shadowDy
1679     * @attr ref android.R.styleable#TextView_shadowRadius
1680     */
1681    public void setShadowLayer(float radius, float dx, float dy, int color) {
1682        mTextPaint.setShadowLayer(radius, dx, dy, color);
1683
1684        mShadowRadius = radius;
1685        mShadowDx = dx;
1686        mShadowDy = dy;
1687
1688        invalidate();
1689    }
1690
1691    /**
1692     * @return the base paint used for the text.  Please use this only to
1693     * consult the Paint's properties and not to change them.
1694     */
1695    public TextPaint getPaint() {
1696        return mTextPaint;
1697    }
1698
1699    /**
1700     * Sets the autolink mask of the text.  See {@link
1701     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1702     * possible values.
1703     *
1704     * @attr ref android.R.styleable#TextView_autoLink
1705     */
1706    public final void setAutoLinkMask(int mask) {
1707        mAutoLinkMask = mask;
1708    }
1709
1710    /**
1711     * Sets whether the movement method will automatically be set to
1712     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1713     * set to nonzero and links are detected in {@link #setText}.
1714     * The default is true.
1715     *
1716     * @attr ref android.R.styleable#TextView_linksClickable
1717     */
1718    public final void setLinksClickable(boolean whether) {
1719        mLinksClickable = whether;
1720    }
1721
1722    /**
1723     * Returns whether the movement method will automatically be set to
1724     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1725     * set to nonzero and links are detected in {@link #setText}.
1726     * The default is true.
1727     *
1728     * @attr ref android.R.styleable#TextView_linksClickable
1729     */
1730    public final boolean getLinksClickable() {
1731        return mLinksClickable;
1732    }
1733
1734    /**
1735     * Returns the list of URLSpans attached to the text
1736     * (by {@link Linkify} or otherwise) if any.  You can call
1737     * {@link URLSpan#getURL} on them to find where they link to
1738     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
1739     * to find the region of the text they are attached to.
1740     */
1741    public URLSpan[] getUrls() {
1742        if (mText instanceof Spanned) {
1743            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
1744        } else {
1745            return new URLSpan[0];
1746        }
1747    }
1748
1749    /**
1750     * Sets the color of the hint text.
1751     *
1752     * @attr ref android.R.styleable#TextView_textColorHint
1753     */
1754    public final void setHintTextColor(int color) {
1755        mHintTextColor = ColorStateList.valueOf(color);
1756        updateTextColors();
1757    }
1758
1759    /**
1760     * Sets the color of the hint text.
1761     *
1762     * @attr ref android.R.styleable#TextView_textColorHint
1763     */
1764    public final void setHintTextColor(ColorStateList colors) {
1765        mHintTextColor = colors;
1766        updateTextColors();
1767    }
1768
1769    /**
1770     * <p>Return the color used to paint the hint text.</p>
1771     *
1772     * @return Returns the list of hint text colors.
1773     */
1774    public final ColorStateList getHintTextColors() {
1775        return mHintTextColor;
1776    }
1777
1778    /**
1779     * <p>Return the current color selected to paint the hint text.</p>
1780     *
1781     * @return Returns the current hint text color.
1782     */
1783    public final int getCurrentHintTextColor() {
1784        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
1785    }
1786
1787    /**
1788     * Sets the color of links in the text.
1789     *
1790     * @attr ref android.R.styleable#TextView_textColorLink
1791     */
1792    public final void setLinkTextColor(int color) {
1793        mLinkTextColor = ColorStateList.valueOf(color);
1794        updateTextColors();
1795    }
1796
1797    /**
1798     * Sets the color of links in the text.
1799     *
1800     * @attr ref android.R.styleable#TextView_textColorLink
1801     */
1802    public final void setLinkTextColor(ColorStateList colors) {
1803        mLinkTextColor = colors;
1804        updateTextColors();
1805    }
1806
1807    /**
1808     * <p>Returns the color used to paint links in the text.</p>
1809     *
1810     * @return Returns the list of link text colors.
1811     */
1812    public final ColorStateList getLinkTextColors() {
1813        return mLinkTextColor;
1814    }
1815
1816    /**
1817     * Sets the horizontal alignment of the text and the
1818     * vertical gravity that will be used when there is extra space
1819     * in the TextView beyond what is required for the text itself.
1820     *
1821     * @see android.view.Gravity
1822     * @attr ref android.R.styleable#TextView_gravity
1823     */
1824    public void setGravity(int gravity) {
1825        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
1826            gravity |= Gravity.LEFT;
1827        }
1828        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
1829            gravity |= Gravity.TOP;
1830        }
1831
1832        boolean newLayout = false;
1833
1834        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
1835            (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
1836            newLayout = true;
1837        }
1838
1839        if (gravity != mGravity) {
1840            invalidate();
1841        }
1842
1843        mGravity = gravity;
1844
1845        if (mLayout != null && newLayout) {
1846            // XXX this is heavy-handed because no actual content changes.
1847            int want = mLayout.getWidth();
1848            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
1849
1850            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
1851                          mRight - mLeft - getCompoundPaddingLeft() -
1852                          getCompoundPaddingRight(), true);
1853        }
1854    }
1855
1856    /**
1857     * Returns the horizontal and vertical alignment of this TextView.
1858     *
1859     * @see android.view.Gravity
1860     * @attr ref android.R.styleable#TextView_gravity
1861     */
1862    public int getGravity() {
1863        return mGravity;
1864    }
1865
1866    /**
1867     * @return the flags on the Paint being used to display the text.
1868     * @see Paint#getFlags
1869     */
1870    public int getPaintFlags() {
1871        return mTextPaint.getFlags();
1872    }
1873
1874    /**
1875     * Sets flags on the Paint being used to display the text and
1876     * reflows the text if they are different from the old flags.
1877     * @see Paint#setFlags
1878     */
1879    public void setPaintFlags(int flags) {
1880        if (mTextPaint.getFlags() != flags) {
1881            mTextPaint.setFlags(flags);
1882
1883            if (mLayout != null) {
1884                nullLayouts();
1885                requestLayout();
1886                invalidate();
1887            }
1888        }
1889    }
1890
1891    /**
1892     * Sets whether the text should be allowed to be wider than the
1893     * View is.  If false, it will be wrapped to the width of the View.
1894     *
1895     * @attr ref android.R.styleable#TextView_scrollHorizontally
1896     */
1897    public void setHorizontallyScrolling(boolean whether) {
1898        mHorizontallyScrolling = whether;
1899
1900        if (mLayout != null) {
1901            nullLayouts();
1902            requestLayout();
1903            invalidate();
1904        }
1905    }
1906
1907    /**
1908     * Makes the TextView at least this many lines tall
1909     *
1910     * @attr ref android.R.styleable#TextView_minLines
1911     */
1912    public void setMinLines(int minlines) {
1913        mMinimum = minlines;
1914        mMinMode = LINES;
1915
1916        requestLayout();
1917        invalidate();
1918    }
1919
1920    /**
1921     * Makes the TextView at least this many pixels tall
1922     *
1923     * @attr ref android.R.styleable#TextView_minHeight
1924     */
1925    public void setMinHeight(int minHeight) {
1926        mMinimum = minHeight;
1927        mMinMode = PIXELS;
1928
1929        requestLayout();
1930        invalidate();
1931    }
1932
1933    /**
1934     * Makes the TextView at most this many lines tall
1935     *
1936     * @attr ref android.R.styleable#TextView_maxLines
1937     */
1938    public void setMaxLines(int maxlines) {
1939        mMaximum = maxlines;
1940        mMaxMode = LINES;
1941
1942        requestLayout();
1943        invalidate();
1944    }
1945
1946    /**
1947     * Makes the TextView at most this many pixels tall
1948     *
1949     * @attr ref android.R.styleable#TextView_maxHeight
1950     */
1951    public void setMaxHeight(int maxHeight) {
1952        mMaximum = maxHeight;
1953        mMaxMode = PIXELS;
1954
1955        requestLayout();
1956        invalidate();
1957    }
1958
1959    /**
1960     * Makes the TextView exactly this many lines tall
1961     *
1962     * @attr ref android.R.styleable#TextView_lines
1963     */
1964    public void setLines(int lines) {
1965        mMaximum = mMinimum = lines;
1966        mMaxMode = mMinMode = LINES;
1967
1968        requestLayout();
1969        invalidate();
1970    }
1971
1972    /**
1973     * Makes the TextView exactly this many pixels tall.
1974     * You could do the same thing by specifying this number in the
1975     * LayoutParams.
1976     *
1977     * @attr ref android.R.styleable#TextView_height
1978     */
1979    public void setHeight(int pixels) {
1980        mMaximum = mMinimum = pixels;
1981        mMaxMode = mMinMode = PIXELS;
1982
1983        requestLayout();
1984        invalidate();
1985    }
1986
1987    /**
1988     * Makes the TextView at least this many ems wide
1989     *
1990     * @attr ref android.R.styleable#TextView_minEms
1991     */
1992    public void setMinEms(int minems) {
1993        mMinWidth = minems;
1994        mMinWidthMode = EMS;
1995
1996        requestLayout();
1997        invalidate();
1998    }
1999
2000    /**
2001     * Makes the TextView at least this many pixels wide
2002     *
2003     * @attr ref android.R.styleable#TextView_minWidth
2004     */
2005    public void setMinWidth(int minpixels) {
2006        mMinWidth = minpixels;
2007        mMinWidthMode = PIXELS;
2008
2009        requestLayout();
2010        invalidate();
2011    }
2012
2013    /**
2014     * Makes the TextView at most this many ems wide
2015     *
2016     * @attr ref android.R.styleable#TextView_maxEms
2017     */
2018    public void setMaxEms(int maxems) {
2019        mMaxWidth = maxems;
2020        mMaxWidthMode = EMS;
2021
2022        requestLayout();
2023        invalidate();
2024    }
2025
2026    /**
2027     * Makes the TextView at most this many pixels wide
2028     *
2029     * @attr ref android.R.styleable#TextView_maxWidth
2030     */
2031    public void setMaxWidth(int maxpixels) {
2032        mMaxWidth = maxpixels;
2033        mMaxWidthMode = PIXELS;
2034
2035        requestLayout();
2036        invalidate();
2037    }
2038
2039    /**
2040     * Makes the TextView exactly this many ems wide
2041     *
2042     * @attr ref android.R.styleable#TextView_ems
2043     */
2044    public void setEms(int ems) {
2045        mMaxWidth = mMinWidth = ems;
2046        mMaxWidthMode = mMinWidthMode = EMS;
2047
2048        requestLayout();
2049        invalidate();
2050    }
2051
2052    /**
2053     * Makes the TextView exactly this many pixels wide.
2054     * You could do the same thing by specifying this number in the
2055     * LayoutParams.
2056     *
2057     * @attr ref android.R.styleable#TextView_width
2058     */
2059    public void setWidth(int pixels) {
2060        mMaxWidth = mMinWidth = pixels;
2061        mMaxWidthMode = mMinWidthMode = PIXELS;
2062
2063        requestLayout();
2064        invalidate();
2065    }
2066
2067
2068    /**
2069     * Sets line spacing for this TextView.  Each line will have its height
2070     * multiplied by <code>mult</code> and have <code>add</code> added to it.
2071     *
2072     * @attr ref android.R.styleable#TextView_lineSpacingExtra
2073     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
2074     */
2075    public void setLineSpacing(float add, float mult) {
2076        mSpacingMult = mult;
2077        mSpacingAdd = add;
2078
2079        if (mLayout != null) {
2080            nullLayouts();
2081            requestLayout();
2082            invalidate();
2083        }
2084    }
2085
2086    /**
2087     * Convenience method: Append the specified text to the TextView's
2088     * display buffer, upgrading it to BufferType.EDITABLE if it was
2089     * not already editable.
2090     */
2091    public final void append(CharSequence text) {
2092        append(text, 0, text.length());
2093    }
2094
2095    /**
2096     * Convenience method: Append the specified text slice to the TextView's
2097     * display buffer, upgrading it to BufferType.EDITABLE if it was
2098     * not already editable.
2099     */
2100    public void append(CharSequence text, int start, int end) {
2101        if (!(mText instanceof Editable)) {
2102            setText(mText, BufferType.EDITABLE);
2103        }
2104
2105        ((Editable) mText).append(text, start, end);
2106    }
2107
2108    private void updateTextColors() {
2109        boolean inval = false;
2110        int color = mTextColor.getColorForState(getDrawableState(), 0);
2111        if (color != mCurTextColor) {
2112            mCurTextColor = color;
2113            inval = true;
2114        }
2115        if (mLinkTextColor != null) {
2116            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
2117            if (color != mTextPaint.linkColor) {
2118                mTextPaint.linkColor = color;
2119                inval = true;
2120            }
2121        }
2122        if (mHintTextColor != null) {
2123            color = mHintTextColor.getColorForState(getDrawableState(), 0);
2124            if (color != mCurHintTextColor && mText.length() == 0) {
2125                mCurHintTextColor = color;
2126                inval = true;
2127            }
2128        }
2129        if (inval) {
2130            invalidate();
2131        }
2132    }
2133
2134    @Override
2135    protected void drawableStateChanged() {
2136        super.drawableStateChanged();
2137        if (mTextColor != null && mTextColor.isStateful()
2138                || (mHintTextColor != null && mHintTextColor.isStateful())
2139                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
2140            updateTextColors();
2141        }
2142
2143        final Drawables dr = mDrawables;
2144        if (dr != null) {
2145            int[] state = getDrawableState();
2146            if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
2147                dr.mDrawableTop.setState(state);
2148            }
2149            if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
2150                dr.mDrawableBottom.setState(state);
2151            }
2152            if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
2153                dr.mDrawableLeft.setState(state);
2154            }
2155            if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
2156                dr.mDrawableRight.setState(state);
2157            }
2158        }
2159    }
2160
2161    /**
2162     * User interface state that is stored by TextView for implementing
2163     * {@link View#onSaveInstanceState}.
2164     */
2165    public static class SavedState extends BaseSavedState {
2166        int selStart;
2167        int selEnd;
2168        CharSequence text;
2169        boolean frozenWithFocus;
2170
2171        SavedState(Parcelable superState) {
2172            super(superState);
2173        }
2174
2175        @Override
2176        public void writeToParcel(Parcel out, int flags) {
2177            super.writeToParcel(out, flags);
2178            out.writeInt(selStart);
2179            out.writeInt(selEnd);
2180            out.writeInt(frozenWithFocus ? 1 : 0);
2181            TextUtils.writeToParcel(text, out, flags);
2182        }
2183
2184        @Override
2185        public String toString() {
2186            String str = "TextView.SavedState{"
2187                    + Integer.toHexString(System.identityHashCode(this))
2188                    + " start=" + selStart + " end=" + selEnd;
2189            if (text != null) {
2190                str += " text=" + text;
2191            }
2192            return str + "}";
2193        }
2194
2195        public static final Parcelable.Creator<SavedState> CREATOR
2196                = new Parcelable.Creator<SavedState>() {
2197            public SavedState createFromParcel(Parcel in) {
2198                return new SavedState(in);
2199            }
2200
2201            public SavedState[] newArray(int size) {
2202                return new SavedState[size];
2203            }
2204        };
2205
2206        private SavedState(Parcel in) {
2207            super(in);
2208            selStart = in.readInt();
2209            selEnd = in.readInt();
2210            frozenWithFocus = (in.readInt() != 0);
2211            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2212        }
2213    }
2214
2215    @Override
2216    public Parcelable onSaveInstanceState() {
2217        Parcelable superState = super.onSaveInstanceState();
2218
2219        // Save state if we are forced to
2220        boolean save = mFreezesText;
2221        int start = 0;
2222        int end = 0;
2223
2224        if (mText != null) {
2225            start = Selection.getSelectionStart(mText);
2226            end = Selection.getSelectionEnd(mText);
2227            if (start >= 0 || end >= 0) {
2228                // Or save state if there is a selection
2229                save = true;
2230            }
2231        }
2232
2233        if (save) {
2234            SavedState ss = new SavedState(superState);
2235            // XXX Should also save the current scroll position!
2236            ss.selStart = start;
2237            ss.selEnd = end;
2238
2239            if (mText instanceof Spanned) {
2240                /*
2241                 * Calling setText() strips off any ChangeWatchers;
2242                 * strip them now to avoid leaking references.
2243                 * But do it to a copy so that if there are any
2244                 * further changes to the text of this view, it
2245                 * won't get into an inconsistent state.
2246                 */
2247
2248                Spannable sp = new SpannableString(mText);
2249
2250                for (ChangeWatcher cw :
2251                     sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2252                    sp.removeSpan(cw);
2253                }
2254
2255                ss.text = sp;
2256            } else {
2257                ss.text = mText.toString();
2258            }
2259
2260            if (isFocused() && start >= 0 && end >= 0) {
2261                ss.frozenWithFocus = true;
2262            }
2263
2264            return ss;
2265        }
2266
2267        return superState;
2268    }
2269
2270    @Override
2271    public void onRestoreInstanceState(Parcelable state) {
2272        if (!(state instanceof SavedState)) {
2273            super.onRestoreInstanceState(state);
2274            return;
2275        }
2276
2277        SavedState ss = (SavedState)state;
2278        super.onRestoreInstanceState(ss.getSuperState());
2279
2280        // XXX restore buffer type too, as well as lots of other stuff
2281        if (ss.text != null) {
2282            setText(ss.text);
2283        }
2284
2285        if (ss.selStart >= 0 && ss.selEnd >= 0) {
2286            if (mText instanceof Spannable) {
2287                int len = mText.length();
2288
2289                if (ss.selStart > len || ss.selEnd > len) {
2290                    String restored = "";
2291
2292                    if (ss.text != null) {
2293                        restored = "(restored) ";
2294                    }
2295
2296                    Log.e("TextView", "Saved cursor position " + ss.selStart +
2297                          "/" + ss.selEnd + " out of range for " + restored +
2298                          "text " + mText);
2299                } else {
2300                    Selection.setSelection((Spannable) mText, ss.selStart,
2301                                           ss.selEnd);
2302
2303                    if (ss.frozenWithFocus) {
2304                        mFrozenWithFocus = true;
2305                    }
2306                }
2307            }
2308        }
2309    }
2310
2311    /**
2312     * Control whether this text view saves its entire text contents when
2313     * freezing to an icicle, in addition to dynamic state such as cursor
2314     * position.  By default this is false, not saving the text.  Set to true
2315     * if the text in the text view is not being saved somewhere else in
2316     * persistent storage (such as in a content provider) so that if the
2317     * view is later thawed the user will not lose their data.
2318     *
2319     * @param freezesText Controls whether a frozen icicle should include the
2320     * entire text data: true to include it, false to not.
2321     *
2322     * @attr ref android.R.styleable#TextView_freezesText
2323     */
2324    public void setFreezesText(boolean freezesText) {
2325        mFreezesText = freezesText;
2326    }
2327
2328    /**
2329     * Return whether this text view is including its entire text contents
2330     * in frozen icicles.
2331     *
2332     * @return Returns true if text is included, false if it isn't.
2333     *
2334     * @see #setFreezesText
2335     */
2336    public boolean getFreezesText() {
2337        return mFreezesText;
2338    }
2339
2340    ///////////////////////////////////////////////////////////////////////////
2341
2342    /**
2343     * Sets the Factory used to create new Editables.
2344     */
2345    public final void setEditableFactory(Editable.Factory factory) {
2346        mEditableFactory = factory;
2347        setText(mText);
2348    }
2349
2350    /**
2351     * Sets the Factory used to create new Spannables.
2352     */
2353    public final void setSpannableFactory(Spannable.Factory factory) {
2354        mSpannableFactory = factory;
2355        setText(mText);
2356    }
2357
2358    /**
2359     * Sets the string value of the TextView. TextView <em>does not</em> accept
2360     * HTML-like formatting, which you can do with text strings in XML resource files.
2361     * To style your strings, attach android.text.style.* objects to a
2362     * {@link android.text.SpannableString SpannableString}, or see the
2363     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
2364     * Available Resource Types</a> documentation for an example of setting
2365     * formatted text in the XML resource file.
2366     *
2367     * @attr ref android.R.styleable#TextView_text
2368     */
2369    public final void setText(CharSequence text) {
2370        setText(text, mBufferType);
2371    }
2372
2373    /**
2374     * Like {@link #setText(CharSequence)},
2375     * except that the cursor position (if any) is retained in the new text.
2376     *
2377     * @param text The new text to place in the text view.
2378     *
2379     * @see #setText(CharSequence)
2380     */
2381    public final void setTextKeepState(CharSequence text) {
2382        setTextKeepState(text, mBufferType);
2383    }
2384
2385    /**
2386     * Sets the text that this TextView is to display (see
2387     * {@link #setText(CharSequence)}) and also sets whether it is stored
2388     * in a styleable/spannable buffer and whether it is editable.
2389     *
2390     * @attr ref android.R.styleable#TextView_text
2391     * @attr ref android.R.styleable#TextView_bufferType
2392     */
2393    public void setText(CharSequence text, BufferType type) {
2394        setText(text, type, true, 0);
2395
2396        if (mCharWrapper != null) {
2397            mCharWrapper.mChars = null;
2398        }
2399    }
2400
2401    private void setText(CharSequence text, BufferType type,
2402                         boolean notifyBefore, int oldlen) {
2403        if (text == null) {
2404            text = "";
2405        }
2406
2407        if (text instanceof Spanned &&
2408            ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
2409            setHorizontalFadingEdgeEnabled(true);
2410            setEllipsize(TextUtils.TruncateAt.MARQUEE);
2411        }
2412
2413        int n = mFilters.length;
2414        for (int i = 0; i < n; i++) {
2415            CharSequence out = mFilters[i].filter(text, 0, text.length(),
2416                                                  EMPTY_SPANNED, 0, 0);
2417            if (out != null) {
2418                text = out;
2419            }
2420        }
2421
2422        if (notifyBefore) {
2423            if (mText != null) {
2424                oldlen = mText.length();
2425                sendBeforeTextChanged(mText, 0, oldlen, text.length());
2426            } else {
2427                sendBeforeTextChanged("", 0, 0, text.length());
2428            }
2429        }
2430
2431        boolean needEditableForNotification = false;
2432
2433        if (mListeners != null && mListeners.size() != 0) {
2434            needEditableForNotification = true;
2435        }
2436
2437        if (type == BufferType.EDITABLE || mInput != null ||
2438            needEditableForNotification) {
2439            Editable t = mEditableFactory.newEditable(text);
2440            text = t;
2441            setFilters(t, mFilters);
2442            InputMethodManager imm = InputMethodManager.peekInstance();
2443            if (imm != null) imm.restartInput(this);
2444        } else if (type == BufferType.SPANNABLE || mMovement != null) {
2445            text = mSpannableFactory.newSpannable(text);
2446        } else if (!(text instanceof CharWrapper)) {
2447            text = TextUtils.stringOrSpannedString(text);
2448        }
2449
2450        if (mAutoLinkMask != 0) {
2451            Spannable s2;
2452
2453            if (type == BufferType.EDITABLE || text instanceof Spannable) {
2454                s2 = (Spannable) text;
2455            } else {
2456                s2 = mSpannableFactory.newSpannable(text);
2457            }
2458
2459            if (Linkify.addLinks(s2, mAutoLinkMask)) {
2460                text = s2;
2461                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
2462
2463                /*
2464                 * We must go ahead and set the text before changing the
2465                 * movement method, because setMovementMethod() may call
2466                 * setText() again to try to upgrade the buffer type.
2467                 */
2468                mText = text;
2469
2470                if (mLinksClickable) {
2471                    setMovementMethod(LinkMovementMethod.getInstance());
2472                }
2473            }
2474        }
2475
2476        mBufferType = type;
2477        mText = text;
2478
2479        if (mTransformation == null)
2480            mTransformed = text;
2481        else
2482            mTransformed = mTransformation.getTransformation(text, this);
2483
2484        final int textLength = text.length();
2485
2486        if (text instanceof Spannable) {
2487            Spannable sp = (Spannable) text;
2488
2489            // Remove any ChangeWatchers that might have come
2490            // from other TextViews.
2491            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
2492            final int count = watchers.length;
2493            for (int i = 0; i < count; i++)
2494                sp.removeSpan(watchers[i]);
2495
2496            if (mChangeWatcher == null)
2497                mChangeWatcher = new ChangeWatcher();
2498
2499            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
2500                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
2501
2502            if (mInput != null) {
2503                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2504            }
2505
2506            if (mTransformation != null) {
2507                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2508
2509            }
2510
2511            if (mMovement != null) {
2512                mMovement.initialize(this, (Spannable) text);
2513
2514                /*
2515                 * Initializing the movement method will have set the
2516                 * selection, so reset mSelectionMoved to keep that from
2517                 * interfering with the normal on-focus selection-setting.
2518                 */
2519                mSelectionMoved = false;
2520            }
2521        }
2522
2523        if (mLayout != null) {
2524            checkForRelayout();
2525        }
2526
2527        sendOnTextChanged(text, 0, oldlen, textLength);
2528        onTextChanged(text, 0, oldlen, textLength);
2529
2530        if (needEditableForNotification) {
2531            sendAfterTextChanged((Editable) text);
2532        }
2533    }
2534
2535    /**
2536     * Sets the TextView to display the specified slice of the specified
2537     * char array.  You must promise that you will not change the contents
2538     * of the array except for right before another call to setText(),
2539     * since the TextView has no way to know that the text
2540     * has changed and that it needs to invalidate and re-layout.
2541     */
2542    public final void setText(char[] text, int start, int len) {
2543        int oldlen = 0;
2544
2545        if (start < 0 || len < 0 || start + len > text.length) {
2546            throw new IndexOutOfBoundsException(start + ", " + len);
2547        }
2548
2549        /*
2550         * We must do the before-notification here ourselves because if
2551         * the old text is a CharWrapper we destroy it before calling
2552         * into the normal path.
2553         */
2554        if (mText != null) {
2555            oldlen = mText.length();
2556            sendBeforeTextChanged(mText, 0, oldlen, len);
2557        } else {
2558            sendBeforeTextChanged("", 0, 0, len);
2559        }
2560
2561        if (mCharWrapper == null) {
2562            mCharWrapper = new CharWrapper(text, start, len);
2563        } else {
2564            mCharWrapper.set(text, start, len);
2565        }
2566
2567        setText(mCharWrapper, mBufferType, false, oldlen);
2568    }
2569
2570    private static class CharWrapper
2571            implements CharSequence, GetChars, GraphicsOperations {
2572        private char[] mChars;
2573        private int mStart, mLength;
2574
2575        public CharWrapper(char[] chars, int start, int len) {
2576            mChars = chars;
2577            mStart = start;
2578            mLength = len;
2579        }
2580
2581        /* package */ void set(char[] chars, int start, int len) {
2582            mChars = chars;
2583            mStart = start;
2584            mLength = len;
2585        }
2586
2587        public int length() {
2588            return mLength;
2589        }
2590
2591        public char charAt(int off) {
2592            return mChars[off + mStart];
2593        }
2594
2595        public String toString() {
2596            return new String(mChars, mStart, mLength);
2597        }
2598
2599        public CharSequence subSequence(int start, int end) {
2600            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2601                throw new IndexOutOfBoundsException(start + ", " + end);
2602            }
2603
2604            return new String(mChars, start + mStart, end - start);
2605        }
2606
2607        public void getChars(int start, int end, char[] buf, int off) {
2608            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2609                throw new IndexOutOfBoundsException(start + ", " + end);
2610            }
2611
2612            System.arraycopy(mChars, start + mStart, buf, off, end - start);
2613        }
2614
2615        public void drawText(Canvas c, int start, int end,
2616                             float x, float y, Paint p) {
2617            c.drawText(mChars, start + mStart, end - start, x, y, p);
2618        }
2619
2620        public float measureText(int start, int end, Paint p) {
2621            return p.measureText(mChars, start + mStart, end - start);
2622        }
2623
2624        public int getTextWidths(int start, int end, float[] widths, Paint p) {
2625            return p.getTextWidths(mChars, start + mStart, end - start, widths);
2626        }
2627    }
2628
2629    /**
2630     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2631     * except that the cursor position (if any) is retained in the new text.
2632     *
2633     * @see #setText(CharSequence, android.widget.TextView.BufferType)
2634     */
2635    public final void setTextKeepState(CharSequence text, BufferType type) {
2636        int start = getSelectionStart();
2637        int end = getSelectionEnd();
2638        int len = text.length();
2639
2640        setText(text, type);
2641
2642        if (start >= 0 || end >= 0) {
2643            if (mText instanceof Spannable) {
2644                Selection.setSelection((Spannable) mText,
2645                                       Math.max(0, Math.min(start, len)),
2646                                       Math.max(0, Math.min(end, len)));
2647            }
2648        }
2649    }
2650
2651    public final void setText(int resid) {
2652        setText(getContext().getResources().getText(resid));
2653    }
2654
2655    public final void setText(int resid, BufferType type) {
2656        setText(getContext().getResources().getText(resid), type);
2657    }
2658
2659    /**
2660     * Sets the text to be displayed when the text of the TextView is empty.
2661     * Null means to use the normal empty text. The hint does not currently
2662     * participate in determining the size of the view.
2663     *
2664     * This method is deprecated. Use {link #setHint(int, String)} or
2665     * {link #setHint(CharSequence, String)} instead.
2666     *
2667     * @attr ref android.R.styleable#TextView_hint
2668     */
2669    public final void setHint(CharSequence hint) {
2670        mHint = TextUtils.stringOrSpannedString(hint);
2671
2672        if (mLayout != null) {
2673            checkForRelayout();
2674        }
2675
2676        if (mText.length() == 0)
2677            invalidate();
2678    }
2679
2680    /**
2681     * Sets the text to be displayed when the text of the TextView is empty,
2682     * from a resource.
2683     *
2684     * This method is deprecated. Use {link #setHint(int, String)} or
2685     * {link #setHint(CharSequence, String)} instead.
2686     *
2687     * @attr ref android.R.styleable#TextView_hint
2688     */
2689    public final void setHint(int resid) {
2690        setHint(getContext().getResources().getText(resid));
2691    }
2692
2693    /**
2694     * Returns the hint that is displayed when the text of the TextView
2695     * is empty.
2696     *
2697     * @attr ref android.R.styleable#TextView_hint
2698     */
2699    @ViewDebug.CapturedViewProperty
2700    public CharSequence getHint() {
2701        return mHint;
2702    }
2703
2704    /**
2705     * Set the type of the content with a constant as defined for
2706     * {@link EditorInfo#inputType}.  This will take care of changing
2707     * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
2708     * match the given content type.  If the given content type is
2709     * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
2710     * not be displayed for this text view.
2711     *
2712     * @see #getInputType()
2713     * @see #setRawInputType(int)
2714     * @see android.text.InputType
2715     * @attr ref android.R.styleable#TextView_inputType
2716     */
2717    public void setInputType(int type) {
2718        setInputType(type, false);
2719        final boolean isPassword = (type&(EditorInfo.TYPE_MASK_CLASS
2720                |EditorInfo.TYPE_MASK_VARIATION))
2721                == (EditorInfo.TYPE_CLASS_TEXT
2722                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
2723        boolean forceUpdate = false;
2724        if (isPassword) {
2725            setTransformationMethod(PasswordTransformationMethod.getInstance());
2726            setTypefaceByIndex(MONOSPACE, 0);
2727        } else if (mTransformation == PasswordTransformationMethod.getInstance()) {
2728            // We need to clean up if we were previously in password mode.
2729            setTypefaceByIndex(-1, -1);
2730            forceUpdate = true;
2731        }
2732
2733        boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
2734                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
2735                (EditorInfo.TYPE_CLASS_TEXT
2736                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
2737
2738        // We need to update the single line mode if it has changed or we
2739        // were previously in password mode.
2740        if (mSingleLine == multiLine || forceUpdate) {
2741            // Change single line mode, but only change the transformation if
2742            // we are not in password mode.
2743            applySingleLine(!multiLine, !isPassword);
2744        }
2745
2746        InputMethodManager imm = InputMethodManager.peekInstance();
2747        if (imm != null) imm.restartInput(this);
2748    }
2749
2750    /**
2751     * Directly change the content type integer of the text view, without
2752     * modifying any other state.
2753     * @see #setContentType
2754     * @see android.text.InputType
2755     * @attr ref android.R.styleable#TextView_inputType
2756     */
2757    public void setRawInputType(int type) {
2758        mInputType = type;
2759    }
2760
2761    private void setInputType(int type, boolean direct) {
2762        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
2763        KeyListener input;
2764        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
2765            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
2766                    != 0;
2767            TextKeyListener.Capitalize cap;
2768            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
2769                cap = TextKeyListener.Capitalize.CHARACTERS;
2770            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
2771                cap = TextKeyListener.Capitalize.WORDS;
2772            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
2773                cap = TextKeyListener.Capitalize.SENTENCES;
2774            } else {
2775                cap = TextKeyListener.Capitalize.NONE;
2776            }
2777            input = TextKeyListener.getInstance(autotext, cap);
2778        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
2779            input = DigitsKeyListener.getInstance(
2780                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
2781                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
2782        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
2783            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
2784                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
2785                    input = DateKeyListener.getInstance();
2786                    break;
2787                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
2788                    input = TimeKeyListener.getInstance();
2789                    break;
2790                default:
2791                    input = DateTimeKeyListener.getInstance();
2792                    break;
2793            }
2794        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
2795            input = DialerKeyListener.getInstance();
2796        } else {
2797            input = TextKeyListener.getInstance();
2798        }
2799        mInputType = type;
2800        if (direct) mInput = input;
2801        else {
2802            setKeyListenerOnly(input);
2803        }
2804    }
2805
2806    /**
2807     * Get the type of the content.
2808     *
2809     * @see #setInputType(int)
2810     * @see android.text.InputType
2811     */
2812    public int getInputType() {
2813        return mInputType;
2814    }
2815
2816    /**
2817     * Set the private content type of the text, which is the
2818     * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType}
2819     * field that will be filled in when creating an input connection.
2820     *
2821     * @see #getPrivateContentType()
2822     * @see EditorInfo#privateContentType
2823     * @attr ref android.R.styleable#TextView_editorPrivateContentType
2824     */
2825    public void setPrivateContentType(String type) {
2826        if (mInputContentType == null) mInputContentType = new InputContentType();
2827        mInputContentType.privateContentType = type;
2828    }
2829
2830    /**
2831     * Get the private type of the content.
2832     *
2833     * @see #setPrivateContentType(String)
2834     * @see EditorInfo#privateContentType
2835     */
2836    public String getPrivateContentType() {
2837        return mInputContentType != null
2838                ? mInputContentType.privateContentType : null;
2839    }
2840
2841    /**
2842     * Set the extra input data of the text, which is the
2843     * {@link EditorInfo#extras TextBoxAttribute.extras}
2844     * Bundle that will be filled in when creating an input connection.  The
2845     * given integer is the resource ID of an XML resource holding an
2846     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
2847     *
2848     * @see #getInputExtras(boolean)
2849     * @see EditorInfo#extras
2850     * @attr ref android.R.styleable#TextView_editorExtras
2851     */
2852    public void setInputExtras(int xmlResId)
2853            throws XmlPullParserException, IOException {
2854        XmlResourceParser parser = getResources().getXml(xmlResId);
2855        if (mInputContentType == null) mInputContentType = new InputContentType();
2856        mInputContentType.extras = new Bundle();
2857        getResources().parseBundleExtras(parser, mInputContentType.extras);
2858    }
2859
2860    /**
2861     * Retrieve the input extras currently associated with the text view, which
2862     * can be viewed as well as modified.
2863     *
2864     * @param create If true, the extras will be created if they don't already
2865     * exist.  Otherwise, null will be returned if none have been created.
2866     * @see #setInputExtras(int)View
2867     * @see EditorInfo#extras
2868     * @attr ref android.R.styleable#TextView_editorExtras
2869     */
2870    public Bundle getInputExtras(boolean create) {
2871        if (mInputContentType == null) {
2872            if (!create) return null;
2873            mInputContentType = new InputContentType();
2874        }
2875        if (mInputContentType.extras == null) {
2876            if (!create) return null;
2877            mInputContentType.extras = new Bundle();
2878        }
2879        return mInputContentType.extras;
2880    }
2881
2882    /**
2883     * Returns the error message that was set to be displayed with
2884     * {@link #setError}, or <code>null</code> if no error was set
2885     * or if it the error was cleared by the widget after user input.
2886     */
2887    public CharSequence getError() {
2888        return mError;
2889    }
2890
2891    /**
2892     * Sets the right-hand compound drawable of the TextView to the "error"
2893     * icon and sets an error message that will be displayed in a popup when
2894     * the TextView has focus.  The icon and error message will be reset to
2895     * null when any key events cause changes to the TextView's text.  If the
2896     * <code>error</code> is <code>null</code>, the error message and icon
2897     * will be cleared.
2898     */
2899    public void setError(CharSequence error) {
2900        if (error == null) {
2901            setError(null, null);
2902        } else {
2903            Drawable dr = getContext().getResources().
2904                getDrawable(com.android.internal.R.drawable.
2905                            indicator_input_error);
2906
2907            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
2908            setError(error, dr);
2909        }
2910    }
2911
2912    /**
2913     * Sets the right-hand compound drawable of the TextView to the specified
2914     * icon and sets an error message that will be displayed in a popup when
2915     * the TextView has focus.  The icon and error message will be reset to
2916     * null when any key events cause changes to the TextView's text.  The
2917     * drawable must already have had {@link Drawable#setBounds} set on it.
2918     * If the <code>error</code> is <code>null</code>, the error message will
2919     * be cleared (and you should provide a <code>null</code> icon as well).
2920     */
2921    public void setError(CharSequence error, Drawable icon) {
2922        error = TextUtils.stringOrSpannedString(error);
2923
2924        mError = error;
2925        mErrorWasChanged = true;
2926        final Drawables dr = mDrawables;
2927        if (dr != null) {
2928            setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
2929                                 icon, dr.mDrawableBottom);
2930        } else {
2931            setCompoundDrawables(null, null, icon, null);
2932        }
2933
2934        if (error == null) {
2935            if (mPopup != null) {
2936                if (mPopup.isShowing()) {
2937                    mPopup.dismiss();
2938                }
2939
2940                mPopup = null;
2941            }
2942        } else {
2943            if (isFocused()) {
2944                showError();
2945            }
2946        }
2947    }
2948
2949    private void showError() {
2950        if (getWindowToken() == null) {
2951            mShowErrorAfterAttach = true;
2952            return;
2953        }
2954
2955        if (mPopup == null) {
2956            LayoutInflater inflater = LayoutInflater.from(getContext());
2957            TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
2958                    null);
2959
2960            mPopup = new PopupWindow(err, 200, 50);
2961            mPopup.setFocusable(false);
2962        }
2963
2964        TextView tv = (TextView) mPopup.getContentView();
2965        chooseSize(mPopup, mError, tv);
2966        tv.setText(mError);
2967
2968        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
2969    }
2970
2971    /**
2972     * Returns the Y offset to make the pointy top of the error point
2973     * at the middle of the error icon.
2974     */
2975    private int getErrorX() {
2976        /*
2977         * The "25" is the distance between the point and the right edge
2978         * of the background
2979         */
2980
2981        final Drawables dr = mDrawables;
2982        return getWidth() - mPopup.getWidth()
2983                - getPaddingRight()
2984                - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25;
2985    }
2986
2987    /**
2988     * Returns the Y offset to make the pointy top of the error point
2989     * at the bottom of the error icon.
2990     */
2991    private int getErrorY() {
2992        /*
2993         * Compound, not extended, because the icon is not clipped
2994         * if the text height is smaller.
2995         */
2996        int vspace = mBottom - mTop -
2997                     getCompoundPaddingBottom() - getCompoundPaddingTop();
2998
2999        final Drawables dr = mDrawables;
3000        int icontop = getCompoundPaddingTop()
3001                + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
3002
3003        /*
3004         * The "2" is the distance between the point and the top edge
3005         * of the background.
3006         */
3007
3008        return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
3009                - getHeight() - 2;
3010    }
3011
3012    private void hideError() {
3013        if (mPopup != null) {
3014            if (mPopup.isShowing()) {
3015                mPopup.dismiss();
3016            }
3017        }
3018
3019        mShowErrorAfterAttach = false;
3020    }
3021
3022    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
3023        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
3024        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
3025
3026        /*
3027         * Figure out how big the text would be if we laid it out to the
3028         * full width of this view minus the border.
3029         */
3030        int cap = getWidth() - wid;
3031        if (cap < 0) {
3032            cap = 200; // We must not be measured yet -- setFrame() will fix it.
3033        }
3034
3035        Layout l = new StaticLayout(text, tv.getPaint(), cap,
3036                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
3037        float max = 0;
3038        for (int i = 0; i < l.getLineCount(); i++) {
3039            max = Math.max(max, l.getLineWidth(i));
3040        }
3041
3042        /*
3043         * Now set the popup size to be big enough for the text plus the border.
3044         */
3045        pop.setWidth(wid + (int) Math.ceil(max));
3046        pop.setHeight(ht + l.getHeight());
3047    }
3048
3049
3050    @Override
3051    protected boolean setFrame(int l, int t, int r, int b) {
3052        boolean result = super.setFrame(l, t, r, b);
3053
3054        if (mPopup != null) {
3055            TextView tv = (TextView) mPopup.getContentView();
3056            chooseSize(mPopup, mError, tv);
3057            mPopup.update(this, getErrorX(), getErrorY(), -1, -1);
3058        }
3059
3060        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3061            mRestartMarquee = false;
3062            startMarquee();
3063        }
3064
3065        return result;
3066    }
3067
3068    /**
3069     * Sets the list of input filters that will be used if the buffer is
3070     * Editable.  Has no effect otherwise.
3071     *
3072     * @attr ref android.R.styleable#TextView_maxLength
3073     */
3074    public void setFilters(InputFilter[] filters) {
3075        if (filters == null) {
3076            throw new IllegalArgumentException();
3077        }
3078
3079        mFilters = filters;
3080
3081        if (mText instanceof Editable) {
3082            setFilters((Editable) mText, filters);
3083        }
3084    }
3085
3086    /**
3087     * Sets the list of input filters on the specified Editable,
3088     * and includes mInput in the list if it is an InputFilter.
3089     */
3090    private void setFilters(Editable e, InputFilter[] filters) {
3091        if (mInput instanceof InputFilter) {
3092            InputFilter[] nf = new InputFilter[filters.length + 1];
3093
3094            System.arraycopy(filters, 0, nf, 0, filters.length);
3095            nf[filters.length] = (InputFilter) mInput;
3096
3097            e.setFilters(nf);
3098        } else {
3099            e.setFilters(filters);
3100        }
3101    }
3102
3103    /**
3104     * Returns the current list of input filters.
3105     */
3106    public InputFilter[] getFilters() {
3107        return mFilters;
3108    }
3109
3110    /////////////////////////////////////////////////////////////////////////
3111
3112    private int getVerticalOffset(boolean forceNormal) {
3113        int voffset = 0;
3114        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3115
3116        Layout l = mLayout;
3117        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3118            l = mHintLayout;
3119        }
3120
3121        if (gravity != Gravity.TOP) {
3122            int boxht;
3123
3124            if (l == mHintLayout) {
3125                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3126                        getCompoundPaddingBottom();
3127            } else {
3128                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3129                        getExtendedPaddingBottom();
3130            }
3131            int textht = l.getHeight();
3132
3133            if (textht < boxht) {
3134                if (gravity == Gravity.BOTTOM)
3135                    voffset = boxht - textht;
3136                else // (gravity == Gravity.CENTER_VERTICAL)
3137                    voffset = (boxht - textht) >> 1;
3138            }
3139        }
3140        return voffset;
3141    }
3142
3143    private int getBottomVerticalOffset(boolean forceNormal) {
3144        int voffset = 0;
3145        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3146
3147        Layout l = mLayout;
3148        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3149            l = mHintLayout;
3150        }
3151
3152        if (gravity != Gravity.BOTTOM) {
3153            int boxht;
3154
3155            if (l == mHintLayout) {
3156                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3157                        getCompoundPaddingBottom();
3158            } else {
3159                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3160                        getExtendedPaddingBottom();
3161            }
3162            int textht = l.getHeight();
3163
3164            if (textht < boxht) {
3165                if (gravity == Gravity.TOP)
3166                    voffset = boxht - textht;
3167                else // (gravity == Gravity.CENTER_VERTICAL)
3168                    voffset = (boxht - textht) >> 1;
3169            }
3170        }
3171        return voffset;
3172    }
3173
3174    private void invalidateCursorPath() {
3175        if (mHighlightPathBogus) {
3176            invalidateCursor();
3177        } else {
3178            synchronized (sTempRect) {
3179                /*
3180                 * The reason for this concern about the thickness of the
3181                 * cursor and doing the floor/ceil on the coordinates is that
3182                 * some EditTexts (notably textfields in the Browser) have
3183                 * anti-aliased text where not all the characters are
3184                 * necessarily at integer-multiple locations.  This should
3185                 * make sure the entire cursor gets invalidated instead of
3186                 * sometimes missing half a pixel.
3187                 */
3188
3189                float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
3190                if (thick < 1.0f) {
3191                    thick = 1.0f;
3192                }
3193
3194                thick /= 2;
3195
3196                mHighlightPath.computeBounds(sTempRect, false);
3197
3198                int left = getCompoundPaddingLeft();
3199                int top = getExtendedPaddingTop() + getVerticalOffset(true);
3200
3201                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
3202                           (int) FloatMath.floor(top + sTempRect.top - thick),
3203                           (int) FloatMath.ceil(left + sTempRect.right + thick),
3204                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
3205            }
3206        }
3207    }
3208
3209    private void invalidateCursor() {
3210        int where = Selection.getSelectionEnd(mText);
3211
3212        invalidateCursor(where, where, where);
3213    }
3214
3215    private void invalidateCursor(int a, int b, int c) {
3216        if (mLayout == null) {
3217            invalidate();
3218        } else {
3219            if (a >= 0 || b >= 0 || c >= 0) {
3220                int first = Math.min(Math.min(a, b), c);
3221                int last = Math.max(Math.max(a, b), c);
3222
3223                int line = mLayout.getLineForOffset(first);
3224                int top = mLayout.getLineTop(line);
3225
3226                // This is ridiculous, but the descent from the line above
3227                // can hang down into the line we really want to redraw,
3228                // so we have to invalidate part of the line above to make
3229                // sure everything that needs to be redrawn really is.
3230                // (But not the whole line above, because that would cause
3231                // the same problem with the descenders on the line above it!)
3232                if (line > 0) {
3233                    top -= mLayout.getLineDescent(line - 1);
3234                }
3235
3236                int line2;
3237
3238                if (first == last)
3239                    line2 = line;
3240                else
3241                    line2 = mLayout.getLineForOffset(last);
3242
3243                int bottom = mLayout.getLineTop(line2 + 1);
3244                int voffset = getVerticalOffset(true);
3245
3246                int left = getCompoundPaddingLeft() + mScrollX;
3247                invalidate(left, top + voffset + getExtendedPaddingTop(),
3248                           left + getWidth() - getCompoundPaddingLeft() -
3249                           getCompoundPaddingRight(),
3250                           bottom + voffset + getExtendedPaddingTop());
3251            }
3252        }
3253    }
3254
3255    private void registerForPreDraw() {
3256        final ViewTreeObserver observer = getViewTreeObserver();
3257        if (observer == null) {
3258            return;
3259        }
3260
3261        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
3262            observer.addOnPreDrawListener(this);
3263            mPreDrawState = PREDRAW_PENDING;
3264        } else if (mPreDrawState == PREDRAW_DONE) {
3265            mPreDrawState = PREDRAW_PENDING;
3266        }
3267
3268        // else state is PREDRAW_PENDING, so keep waiting.
3269    }
3270
3271    /**
3272     * {@inheritDoc}
3273     */
3274    public boolean onPreDraw() {
3275        if (mPreDrawState != PREDRAW_PENDING) {
3276            return true;
3277        }
3278
3279        if (mLayout == null) {
3280            assumeLayout();
3281        }
3282
3283        boolean changed = false;
3284
3285        if (mMovement != null) {
3286            int curs = Selection.getSelectionEnd(mText);
3287
3288            /*
3289             * TODO: This should really only keep the end in view if
3290             * it already was before the text changed.  I'm not sure
3291             * of a good way to tell from here if it was.
3292             */
3293            if (curs < 0 &&
3294                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3295                curs = mText.length();
3296            }
3297
3298            if (curs >= 0) {
3299                changed = bringPointIntoView(curs);
3300            }
3301        } else {
3302            changed = bringTextIntoView();
3303        }
3304
3305        mPreDrawState = PREDRAW_DONE;
3306        return !changed;
3307    }
3308
3309    @Override
3310    protected void onAttachedToWindow() {
3311        super.onAttachedToWindow();
3312
3313        if (mShowErrorAfterAttach) {
3314            showError();
3315            mShowErrorAfterAttach = false;
3316        }
3317    }
3318
3319    @Override
3320    protected void onDetachedFromWindow() {
3321        super.onDetachedFromWindow();
3322
3323        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
3324            final ViewTreeObserver observer = getViewTreeObserver();
3325            if (observer != null) {
3326                observer.removeOnPreDrawListener(this);
3327                mPreDrawState = PREDRAW_NOT_REGISTERED;
3328            }
3329        }
3330
3331        if (mError != null) {
3332            hideError();
3333        }
3334    }
3335
3336    @Override
3337    protected boolean isPaddingOffsetRequired() {
3338        return mShadowRadius != 0;
3339    }
3340
3341    @Override
3342    protected int getLeftPaddingOffset() {
3343        return (int) Math.min(0, mShadowDx - mShadowRadius);
3344    }
3345
3346    @Override
3347    protected int getTopPaddingOffset() {
3348        return (int) Math.min(0, mShadowDy - mShadowRadius);
3349    }
3350
3351    @Override
3352    protected int getBottomPaddingOffset() {
3353        return (int) Math.max(0, mShadowDy + mShadowRadius);
3354    }
3355
3356    @Override
3357    protected int getRightPaddingOffset() {
3358        return (int) Math.max(0, mShadowDx + mShadowRadius);
3359    }
3360
3361    @Override
3362    protected boolean verifyDrawable(Drawable who) {
3363        final boolean verified = super.verifyDrawable(who);
3364        if (!verified && mDrawables != null) {
3365            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
3366                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
3367        }
3368        return verified;
3369    }
3370
3371    @Override
3372    protected void onDraw(Canvas canvas) {
3373        // Draw the background for this view
3374        super.onDraw(canvas);
3375
3376        final int compoundPaddingLeft = getCompoundPaddingLeft();
3377        final int compoundPaddingTop = getCompoundPaddingTop();
3378        final int compoundPaddingRight = getCompoundPaddingRight();
3379        final int compoundPaddingBottom = getCompoundPaddingBottom();
3380        final int scrollX = mScrollX;
3381        final int scrollY = mScrollY;
3382        final int right = mRight;
3383        final int left = mLeft;
3384        final int bottom = mBottom;
3385        final int top = mTop;
3386
3387        final Drawables dr = mDrawables;
3388        if (dr != null) {
3389            /*
3390             * Compound, not extended, because the icon is not clipped
3391             * if the text height is smaller.
3392             */
3393
3394            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
3395            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
3396
3397            if (dr.mDrawableLeft != null) {
3398                canvas.save();
3399                canvas.translate(scrollX + mPaddingLeft,
3400                                 scrollY + compoundPaddingTop +
3401                                 (vspace - dr.mDrawableHeightLeft) / 2);
3402                dr.mDrawableLeft.draw(canvas);
3403                canvas.restore();
3404            }
3405
3406            if (dr.mDrawableRight != null) {
3407                canvas.save();
3408                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
3409                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
3410                dr.mDrawableRight.draw(canvas);
3411                canvas.restore();
3412            }
3413
3414            if (dr.mDrawableTop != null) {
3415                canvas.save();
3416                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
3417                        scrollY + mPaddingTop);
3418                dr.mDrawableTop.draw(canvas);
3419                canvas.restore();
3420            }
3421
3422            if (dr.mDrawableBottom != null) {
3423                canvas.save();
3424                canvas.translate(scrollX + compoundPaddingLeft +
3425                        (hspace - dr.mDrawableWidthBottom) / 2,
3426                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
3427                dr.mDrawableBottom.draw(canvas);
3428                canvas.restore();
3429            }
3430        }
3431
3432        if (mPreDrawState == PREDRAW_DONE) {
3433            final ViewTreeObserver observer = getViewTreeObserver();
3434            if (observer != null) {
3435                observer.removeOnPreDrawListener(this);
3436                mPreDrawState = PREDRAW_NOT_REGISTERED;
3437            }
3438        }
3439
3440        int color = mCurTextColor;
3441
3442        if (mLayout == null) {
3443            assumeLayout();
3444        }
3445
3446        Layout layout = mLayout;
3447        int cursorcolor = color;
3448
3449        if (mHint != null && mText.length() == 0) {
3450            if (mHintTextColor != null) {
3451                color = mCurHintTextColor;
3452            }
3453
3454            layout = mHintLayout;
3455        }
3456
3457        mTextPaint.setColor(color);
3458        mTextPaint.drawableState = getDrawableState();
3459
3460        canvas.save();
3461        /*  Would be faster if we didn't have to do this. Can we chop the
3462            (displayable) text so that we don't need to do this ever?
3463        */
3464
3465        int extendedPaddingTop = getExtendedPaddingTop();
3466        int extendedPaddingBottom = getExtendedPaddingBottom();
3467
3468        float clipLeft = compoundPaddingLeft + scrollX;
3469        float clipTop = extendedPaddingTop + scrollY;
3470        float clipRight = right - left - compoundPaddingRight + scrollX;
3471        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
3472
3473        if (mShadowRadius != 0) {
3474            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
3475            clipRight += Math.max(0, mShadowDx + mShadowRadius);
3476
3477            clipTop += Math.min(0, mShadowDy - mShadowRadius);
3478            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
3479        }
3480
3481        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
3482
3483        int voffsetText = 0;
3484        int voffsetCursor = 0;
3485
3486        // translate in by our padding
3487        {
3488            /* shortcircuit calling getVerticaOffset() */
3489            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3490                voffsetText = getVerticalOffset(false);
3491                voffsetCursor = getVerticalOffset(true);
3492            }
3493            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
3494        }
3495
3496        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3497            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
3498                    (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
3499                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
3500                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
3501            }
3502
3503            if (mMarquee != null && mMarquee.isRunning()) {
3504                canvas.translate(-mMarquee.mScroll, 0.0f);
3505            }
3506        }
3507
3508        Path highlight = null;
3509        int selStart = -1, selEnd = -1;
3510
3511        //  If there is no movement method, then there can be no selection.
3512        //  Check that first and attempt to skip everything having to do with
3513        //  the cursor.
3514        //  XXX This is not strictly true -- a program could set the
3515        //  selection manually if it really wanted to.
3516        if (mMovement != null && (isFocused() || isPressed())) {
3517            selStart = Selection.getSelectionStart(mText);
3518            selEnd = Selection.getSelectionEnd(mText);
3519
3520            if (mCursorVisible && selStart >= 0 && isEnabled()) {
3521                if (mHighlightPath == null)
3522                    mHighlightPath = new Path();
3523
3524                if (selStart == selEnd) {
3525                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
3526                        < BLINK) {
3527                        if (mHighlightPathBogus) {
3528                            mHighlightPath.reset();
3529                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
3530                            mHighlightPathBogus = false;
3531                        }
3532
3533                        // XXX should pass to skin instead of drawing directly
3534                        mHighlightPaint.setColor(cursorcolor);
3535                        mHighlightPaint.setStyle(Paint.Style.STROKE);
3536
3537                        highlight = mHighlightPath;
3538                    }
3539                } else {
3540                    if (mHighlightPathBogus) {
3541                        mHighlightPath.reset();
3542                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
3543                        mHighlightPathBogus = false;
3544                    }
3545
3546                    // XXX should pass to skin instead of drawing directly
3547                    mHighlightPaint.setColor(mHighlightColor);
3548                    mHighlightPaint.setStyle(Paint.Style.FILL);
3549
3550                    highlight = mHighlightPath;
3551                }
3552            }
3553        }
3554
3555        /*  Comment out until we decide what to do about animations
3556        boolean isLinearTextOn = false;
3557        if (currentTransformation != null) {
3558            isLinearTextOn = mTextPaint.isLinearTextOn();
3559            Matrix m = currentTransformation.getMatrix();
3560            if (!m.isIdentity()) {
3561                // mTextPaint.setLinearTextOn(true);
3562            }
3563        }
3564        */
3565
3566        final InputMethodState ims = mInputMethodState;
3567        if (ims != null && ims.mBatchEditNesting == 0) {
3568            InputMethodManager imm = InputMethodManager.peekInstance();
3569            if (imm != null) {
3570                if (imm.isActive(this)) {
3571                    boolean reported = false;
3572                    if (ims.mContentChanged) {
3573                        // We are in extract mode and the content has changed
3574                        // in some way... just report complete new text to the
3575                        // input method.
3576                        reported = reportExtractedText();
3577                    }
3578                    if (!reported && highlight != null) {
3579                        int candStart = -1;
3580                        int candEnd = -1;
3581                        if (mText instanceof Spannable) {
3582                            Spannable sp = (Spannable)mText;
3583                            candStart = EditableInputConnection.getComposingSpanStart(sp);
3584                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
3585                        }
3586                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
3587                    }
3588                }
3589
3590                if (imm.isWatchingCursor(this) && highlight != null) {
3591                    highlight.computeBounds(ims.mTmpRectF, true);
3592                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
3593
3594                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
3595                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
3596
3597                    ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
3598
3599                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
3600                            (int)(ims.mTmpRectF.top + 0.5),
3601                            (int)(ims.mTmpRectF.right + 0.5),
3602                            (int)(ims.mTmpRectF.bottom + 0.5));
3603
3604                    imm.updateCursor(this,
3605                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
3606                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
3607                }
3608            }
3609        }
3610
3611        layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
3612
3613        /*  Comment out until we decide what to do about animations
3614        if (currentTransformation != null) {
3615            mTextPaint.setLinearTextOn(isLinearTextOn);
3616        }
3617        */
3618
3619        canvas.restore();
3620    }
3621
3622    @Override
3623    public void getFocusedRect(Rect r) {
3624        if (mLayout == null) {
3625            super.getFocusedRect(r);
3626            return;
3627        }
3628
3629        int sel = getSelectionEnd();
3630        if (sel < 0) {
3631            super.getFocusedRect(r);
3632            return;
3633        }
3634
3635        int line = mLayout.getLineForOffset(sel);
3636        r.top = mLayout.getLineTop(line);
3637        r.bottom = mLayout.getLineBottom(line);
3638
3639        r.left = (int) mLayout.getPrimaryHorizontal(sel);
3640        r.right = r.left + 1;
3641
3642        // Adjust for padding and gravity.
3643        int paddingLeft = getCompoundPaddingLeft();
3644        int paddingTop = getExtendedPaddingTop();
3645        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3646            paddingTop += getVerticalOffset(false);
3647        }
3648        r.offset(paddingLeft, paddingTop);
3649    }
3650
3651    /**
3652     * Return the number of lines of text, or 0 if the internal Layout has not
3653     * been built.
3654     */
3655    public int getLineCount() {
3656        return mLayout != null ? mLayout.getLineCount() : 0;
3657    }
3658
3659    /**
3660     * Return the baseline for the specified line (0...getLineCount() - 1)
3661     * If bounds is not null, return the top, left, right, bottom extents
3662     * of the specified line in it. If the internal Layout has not been built,
3663     * return 0 and set bounds to (0, 0, 0, 0)
3664     * @param line which line to examine (0..getLineCount() - 1)
3665     * @param bounds Optional. If not null, it returns the extent of the line
3666     * @return the Y-coordinate of the baseline
3667     */
3668    public int getLineBounds(int line, Rect bounds) {
3669        if (mLayout == null) {
3670            if (bounds != null) {
3671                bounds.set(0, 0, 0, 0);
3672            }
3673            return 0;
3674        }
3675        else {
3676            int baseline = mLayout.getLineBounds(line, bounds);
3677
3678            int voffset = getExtendedPaddingTop();
3679            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3680                voffset += getVerticalOffset(true);
3681            }
3682            if (bounds != null) {
3683                bounds.offset(getCompoundPaddingLeft(), voffset);
3684            }
3685            return baseline + voffset;
3686        }
3687    }
3688
3689    @Override
3690    public int getBaseline() {
3691        if (mLayout == null) {
3692            return super.getBaseline();
3693        }
3694
3695        int voffset = 0;
3696        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3697            voffset = getVerticalOffset(true);
3698        }
3699
3700        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
3701    }
3702
3703    @Override
3704    public boolean onKeyDown(int keyCode, KeyEvent event) {
3705        int which = doKeyDown(keyCode, event, null);
3706        if (which == 0) {
3707            // Go through default dispatching.
3708            return super.onKeyDown(keyCode, event);
3709        }
3710
3711        return true;
3712    }
3713
3714    @Override
3715    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
3716        KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
3717
3718        int which = doKeyDown(keyCode, down, event);
3719        if (which == 0) {
3720            // Go through default dispatching.
3721            return super.onKeyMultiple(keyCode, repeatCount, event);
3722        }
3723        if (which == -1) {
3724            // Consumed the whole thing.
3725            return true;
3726        }
3727
3728        repeatCount--;
3729
3730        // We are going to dispatch the remaining events to either the input
3731        // or movement method.  To do this, we will just send a repeated stream
3732        // of down and up events until we have done the complete repeatCount.
3733        // It would be nice if those interfaces had an onKeyMultiple() method,
3734        // but adding that is a more complicated change.
3735        KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP);
3736        if (which == 1) {
3737            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
3738            while (--repeatCount > 0) {
3739                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
3740                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
3741            }
3742            if (mError != null && !mErrorWasChanged) {
3743                setError(null, null);
3744            }
3745
3746        } else if (which == 2) {
3747            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
3748            while (--repeatCount > 0) {
3749                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
3750                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
3751            }
3752        }
3753
3754        return true;
3755    }
3756
3757    /**
3758     * Returns true if pressing ENTER in this field advances focus instead
3759     * of inserting the character.  This is true mostly in single-line fields,
3760     * but also in mail addresses and subjects which will display on multiple
3761     * lines but where it doesn't make sense to insert newlines.
3762     */
3763    private boolean advanceFocusOnEnter() {
3764        if (mInput == null) {
3765            return false;
3766        }
3767
3768        if (mSingleLine) {
3769            return true;
3770        }
3771
3772        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
3773            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
3774
3775            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
3776                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
3777                return true;
3778            }
3779        }
3780
3781        return false;
3782    }
3783
3784    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
3785        if (!isEnabled()) {
3786            return 0;
3787        }
3788
3789        switch (keyCode) {
3790            case KeyEvent.KEYCODE_DPAD_CENTER:
3791            case KeyEvent.KEYCODE_ENTER:
3792                if (advanceFocusOnEnter()) {
3793                    return 0;
3794                }
3795        }
3796
3797        if (mInput != null) {
3798            /*
3799             * Keep track of what the error was before doing the input
3800             * so that if an input filter changed the error, we leave
3801             * that error showing.  Otherwise, we take down whatever
3802             * error was showing when the user types something.
3803             */
3804            mErrorWasChanged = false;
3805
3806            boolean doDown = true;
3807            if (otherEvent != null) {
3808                try {
3809                    beginBatchEdit();
3810                    boolean handled = mInput.onKeyOther(this, (Editable) mText,
3811                            otherEvent);
3812                    if (mError != null && !mErrorWasChanged) {
3813                        setError(null, null);
3814                    }
3815                    doDown = false;
3816                    if (handled) {
3817                        return -1;
3818                    }
3819                } catch (AbstractMethodError e) {
3820                    // onKeyOther was added after 1.0, so if it isn't
3821                    // implemented we need to try to dispatch as a regular down.
3822                } finally {
3823                    endBatchEdit();
3824                }
3825            }
3826
3827            if (doDown) {
3828                beginBatchEdit();
3829                if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
3830                    endBatchEdit();
3831                    if (mError != null && !mErrorWasChanged) {
3832                        setError(null, null);
3833                    }
3834                    return 1;
3835                }
3836                endBatchEdit();
3837            }
3838        }
3839
3840        // bug 650865: sometimes we get a key event before a layout.
3841        // don't try to move around if we don't know the layout.
3842
3843        if (mMovement != null && mLayout != null) {
3844            boolean doDown = true;
3845            if (otherEvent != null) {
3846                try {
3847                    boolean handled = mMovement.onKeyOther(this, (Editable) mText,
3848                            otherEvent);
3849                    doDown = false;
3850                    if (handled) {
3851                        return -1;
3852                    }
3853                } catch (AbstractMethodError e) {
3854                    // onKeyOther was added after 1.0, so if it isn't
3855                    // implemented we need to try to dispatch as a regular down.
3856                }
3857            }
3858            if (doDown) {
3859                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
3860                    return 2;
3861            }
3862        }
3863
3864        return 0;
3865    }
3866
3867    @Override
3868    public boolean onKeyUp(int keyCode, KeyEvent event) {
3869        if (!isEnabled()) {
3870            return super.onKeyUp(keyCode, event);
3871        }
3872
3873        switch (keyCode) {
3874            case KeyEvent.KEYCODE_DPAD_CENTER:
3875                /*
3876                 * If there is a click listener, just call through to
3877                 * super, which will invoke it.
3878                 *
3879                 * If there isn't a click listener, try to show the soft
3880                 * input method.  (It will also
3881                 * call performClick(), but that won't do anything in
3882                 * this case.)
3883                 */
3884                if (mOnClickListener == null) {
3885                    if (mMovement != null && mText instanceof Editable
3886                            && mLayout != null && onCheckIsTextEditor()) {
3887                        InputMethodManager imm = (InputMethodManager)
3888                                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
3889                        imm.showSoftInput(this, 0);
3890                    }
3891                }
3892                return super.onKeyUp(keyCode, event);
3893
3894            case KeyEvent.KEYCODE_ENTER:
3895                if (advanceFocusOnEnter()) {
3896                    /*
3897                     * If there is a click listener, just call through to
3898                     * super, which will invoke it.
3899                     *
3900                     * If there isn't a click listener, try to advance focus,
3901                     * but still call through to super, which will reset the
3902                     * pressed state and longpress state.  (It will also
3903                     * call performClick(), but that won't do anything in
3904                     * this case.)
3905                     */
3906                    if (mOnClickListener == null) {
3907                        View v = focusSearch(FOCUS_DOWN);
3908
3909                        if (v != null) {
3910                            if (!v.requestFocus(FOCUS_DOWN)) {
3911                                throw new IllegalStateException("focus search returned a view " +
3912                                        "that wasn't able to take focus!");
3913                            }
3914
3915                            /*
3916                             * Return true because we handled the key; super
3917                             * will return false because there was no click
3918                             * listener.
3919                             */
3920                            super.onKeyUp(keyCode, event);
3921                            return true;
3922                        }
3923                    }
3924
3925                    return super.onKeyUp(keyCode, event);
3926                }
3927        }
3928
3929        if (mInput != null)
3930            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
3931                return true;
3932
3933        if (mMovement != null && mLayout != null)
3934            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
3935                return true;
3936
3937        return super.onKeyUp(keyCode, event);
3938    }
3939
3940    @Override public boolean onCheckIsTextEditor() {
3941        return mInputType != EditorInfo.TYPE_NULL;
3942    }
3943
3944    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
3945        if (onCheckIsTextEditor()) {
3946            if (mInputMethodState == null) {
3947                mInputMethodState = new InputMethodState();
3948            }
3949            outAttrs.inputType = mInputType;
3950            outAttrs.hintText = mHint;
3951            if (mInputContentType != null) {
3952                outAttrs.privateContentType = mInputContentType.privateContentType;
3953                outAttrs.extras = mInputContentType.extras;
3954            }
3955            if (mText instanceof Editable) {
3956                InputConnection ic = new EditableInputConnection(this);
3957                outAttrs.initialSelStart = Selection.getSelectionStart(mText);
3958                outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
3959                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
3960                return ic;
3961            }
3962        }
3963        return null;
3964    }
3965
3966    /**
3967     * If this TextView contains editable content, extract a portion of it
3968     * based on the information in <var>request</var> in to <var>outText</var>.
3969     * @return Returns true if the text was successfully extracted, else false.
3970     */
3971    public boolean extractText(ExtractedTextRequest request,
3972            ExtractedText outText) {
3973        return extractTextInternal(request, -1, -1, -1, outText);
3974    }
3975
3976    boolean extractTextInternal(ExtractedTextRequest request,
3977            int partialStartOffset, int partialEndOffset, int delta,
3978            ExtractedText outText) {
3979        final CharSequence content = mText;
3980        if (content != null) {
3981            final int N = content.length();
3982            if (partialStartOffset < 0) {
3983                outText.partialStartOffset = outText.partialEndOffset = -1;
3984                partialStartOffset = 0;
3985                partialEndOffset = N;
3986            } else {
3987                // Adjust offsets to ensure we contain full spans.
3988                if (content instanceof Spanned) {
3989                    Spanned spanned = (Spanned)content;
3990                    Object[] spans = spanned.getSpans(partialStartOffset,
3991                            partialEndOffset, ParcelableSpan.class);
3992                    int i = spans.length;
3993                    while (i > 0) {
3994                        i--;
3995                        int j = spanned.getSpanStart(spans[i]);
3996                        if (j < partialStartOffset) partialStartOffset = j;
3997                        j = spanned.getSpanEnd(spans[i]);
3998                        if (j > partialEndOffset) partialEndOffset = j;
3999                    }
4000                }
4001                outText.partialStartOffset = partialStartOffset;
4002                outText.partialEndOffset = partialEndOffset;
4003                // Now use the delta to determine the actual amount of text
4004                // we need.
4005                partialEndOffset += delta;
4006                if (partialEndOffset > N) {
4007                    partialEndOffset = N;
4008                } else if (partialEndOffset < 0) {
4009                    partialEndOffset = 0;
4010                }
4011            }
4012            if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
4013                outText.text = content.subSequence(partialStartOffset,
4014                        partialEndOffset);
4015            } else {
4016                outText.text = TextUtils.substring(content, partialStartOffset,
4017                        partialEndOffset);
4018            }
4019            outText.startOffset = 0;
4020            outText.selectionStart = Selection.getSelectionStart(content);
4021            outText.selectionEnd = Selection.getSelectionEnd(content);
4022            return true;
4023        }
4024        return false;
4025    }
4026
4027    boolean reportExtractedText() {
4028        final InputMethodState ims = mInputMethodState;
4029        if (ims != null && ims.mContentChanged) {
4030            ims.mContentChanged = false;
4031            final ExtractedTextRequest req = mInputMethodState.mExtracting;
4032            if (req != null) {
4033                InputMethodManager imm = InputMethodManager.peekInstance();
4034                if (imm != null) {
4035                    if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
4036                            + ims.mChangedStart + " end=" + ims.mChangedEnd
4037                            + " delta=" + ims.mChangedDelta);
4038                    if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
4039                            ims.mChangedDelta, ims.mTmpExtracted)) {
4040                        if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
4041                                + ims.mTmpExtracted.partialStartOffset
4042                                + " end=" + ims.mTmpExtracted.partialEndOffset
4043                                + ": " + ims.mTmpExtracted.text);
4044                        imm.updateExtractedText(this, req.token,
4045                                mInputMethodState.mTmpExtracted);
4046                        return true;
4047                    }
4048                }
4049            }
4050        }
4051        return false;
4052    }
4053
4054    /**
4055     * This is used to remove all style-impacting spans from text before new
4056     * extracted text is being replaced into it, so that we don't have any
4057     * lingering spans applied during the replace.
4058     */
4059    static void removeParcelableSpans(Spannable spannable, int start, int end) {
4060        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
4061        int i = spans.length;
4062        while (i > 0) {
4063            i--;
4064            spannable.removeSpan(spans[i]);
4065        }
4066    }
4067
4068    /**
4069     * Apply to this text view the given extracted text, as previously
4070     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
4071     */
4072    public void setExtractedText(ExtractedText text) {
4073        Editable content = getEditableText();
4074        if (content == null) {
4075            setText(text.text, TextView.BufferType.EDITABLE);
4076        } else if (text.partialStartOffset < 0) {
4077            removeParcelableSpans(content, 0, content.length());
4078            content.replace(0, content.length(), text.text);
4079        } else {
4080            final int N = content.length();
4081            int start = text.partialStartOffset;
4082            if (start > N) start = N;
4083            int end = text.partialEndOffset;
4084            if (end > N) end = N;
4085            removeParcelableSpans(content, start, end);
4086            content.replace(start, end, text.text);
4087        }
4088        Selection.setSelection((Spannable)getText(),
4089                text.selectionStart, text.selectionEnd);
4090    }
4091
4092    /**
4093     * @hide
4094     */
4095    public void setExtracting(ExtractedTextRequest req) {
4096        if (mInputMethodState != null) {
4097            mInputMethodState.mExtracting = req;
4098        }
4099    }
4100
4101    /**
4102     * Called by the framework in response to a text completion from
4103     * the current input method, provided by it calling
4104     * {@link InputConnection#commitCompletion
4105     * InputConnection.commitCompletion()}.  The default implementation does
4106     * nothing; text views that are supporting auto-completion should override
4107     * this to do their desired behavior.
4108     *
4109     * @param text The auto complete text the user has selected.
4110     */
4111    public void onCommitCompletion(CompletionInfo text) {
4112    }
4113
4114    public void beginBatchEdit() {
4115        final InputMethodState ims = mInputMethodState;
4116        if (ims != null) {
4117            int nesting = ++ims.mBatchEditNesting;
4118            if (nesting == 1) {
4119                ims.mCursorChanged = false;
4120                ims.mChangedDelta = 0;
4121                if (ims.mContentChanged) {
4122                    // We already have a pending change from somewhere else,
4123                    // so turn this into a full update.
4124                    ims.mChangedStart = 0;
4125                    ims.mChangedEnd = mText.length();
4126                } else {
4127                    ims.mChangedStart = -1;
4128                    ims.mChangedEnd = -1;
4129                    ims.mContentChanged = false;
4130                }
4131                onBeginBatchEdit();
4132            }
4133        }
4134    }
4135
4136    public void endBatchEdit() {
4137        final InputMethodState ims = mInputMethodState;
4138        if (ims != null) {
4139            int nesting = --ims.mBatchEditNesting;
4140            if (nesting == 0) {
4141                finishBatchEdit(ims);
4142            }
4143        }
4144    }
4145
4146    void ensureEndedBatchEdit() {
4147        final InputMethodState ims = mInputMethodState;
4148        if (ims != null && ims.mBatchEditNesting != 0) {
4149            ims.mBatchEditNesting = 0;
4150            finishBatchEdit(ims);
4151        }
4152    }
4153
4154    void finishBatchEdit(final InputMethodState ims) {
4155        onEndBatchEdit();
4156
4157        if (ims.mContentChanged) {
4158            updateAfterEdit();
4159            reportExtractedText();
4160        } else if (ims.mCursorChanged) {
4161            // Cheezy way to get us to report the current cursor location.
4162            invalidateCursor();
4163        }
4164    }
4165
4166    void updateAfterEdit() {
4167        invalidate();
4168        int curs = Selection.getSelectionStart(mText);
4169
4170        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4171                             Gravity.BOTTOM) {
4172            registerForPreDraw();
4173        }
4174
4175        if (curs >= 0) {
4176            mHighlightPathBogus = true;
4177
4178            if (isFocused()) {
4179                mShowCursor = SystemClock.uptimeMillis();
4180                makeBlink();
4181            }
4182        }
4183
4184        checkForResize();
4185    }
4186
4187    /**
4188     * Called by the framework in response to a request to begin a batch
4189     * of edit operations through a call to link {@link #beginBatchEdit()}.
4190     */
4191    public void onBeginBatchEdit() {
4192    }
4193
4194    /**
4195     * Called by the framework in response to a request to end a batch
4196     * of edit operations through a call to link {@link #endBatchEdit}.
4197     */
4198    public void onEndBatchEdit() {
4199    }
4200
4201    /**
4202     * Called by the framework in response to a private command from the
4203     * current method, provided by it calling
4204     * {@link InputConnection#performPrivateCommand
4205     * InputConnection.performPrivateCommand()}.
4206     *
4207     * @param action The action name of the command.
4208     * @param data Any additional data for the command.  This may be null.
4209     * @return Return true if you handled the command, else false.
4210     */
4211    public boolean onPrivateIMECommand(String action, Bundle data) {
4212        return false;
4213    }
4214
4215    private void nullLayouts() {
4216        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
4217            mSavedLayout = (BoringLayout) mLayout;
4218        }
4219        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
4220            mSavedHintLayout = (BoringLayout) mHintLayout;
4221        }
4222
4223        mLayout = mHintLayout = null;
4224    }
4225
4226    /**
4227     * Make a new Layout based on the already-measured size of the view,
4228     * on the assumption that it was measured correctly at some point.
4229     */
4230    private void assumeLayout() {
4231        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4232
4233        if (width < 1) {
4234            width = 0;
4235        }
4236
4237        int physicalWidth = width;
4238
4239        if (mHorizontallyScrolling) {
4240            width = VERY_WIDE;
4241        }
4242
4243        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
4244                      physicalWidth, false);
4245    }
4246
4247    /**
4248     * The width passed in is now the desired layout width,
4249     * not the full view width with padding.
4250     * {@hide}
4251     */
4252    protected void makeNewLayout(int w, int hintWidth,
4253                                 BoringLayout.Metrics boring,
4254                                 BoringLayout.Metrics hintBoring,
4255                                 int ellipsisWidth, boolean bringIntoView) {
4256        stopMarquee();
4257
4258        mHighlightPathBogus = true;
4259
4260        if (w < 0) {
4261            w = 0;
4262        }
4263        if (hintWidth < 0) {
4264            hintWidth = 0;
4265        }
4266
4267        Layout.Alignment alignment;
4268        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
4269            case Gravity.CENTER_HORIZONTAL:
4270                alignment = Layout.Alignment.ALIGN_CENTER;
4271                break;
4272
4273            case Gravity.RIGHT:
4274                alignment = Layout.Alignment.ALIGN_OPPOSITE;
4275                break;
4276
4277            default:
4278                alignment = Layout.Alignment.ALIGN_NORMAL;
4279        }
4280
4281        if (mText instanceof Spannable) {
4282            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
4283                    alignment, mSpacingMult,
4284                    mSpacingAdd, mIncludePad, mEllipsize,
4285                    ellipsisWidth);
4286        } else {
4287            if (boring == UNKNOWN_BORING) {
4288                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4289                                               mBoring);
4290                if (boring != null) {
4291                    mBoring = boring;
4292                }
4293            }
4294
4295            if (boring != null) {
4296                if (boring.width <= w &&
4297                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
4298                    if (mSavedLayout != null) {
4299                        mLayout = mSavedLayout.
4300                                replaceOrMake(mTransformed, mTextPaint,
4301                                w, alignment, mSpacingMult, mSpacingAdd,
4302                                boring, mIncludePad);
4303                    } else {
4304                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4305                                w, alignment, mSpacingMult, mSpacingAdd,
4306                                boring, mIncludePad);
4307                    }
4308                    // Log.e("aaa", "Boring: " + mTransformed);
4309
4310                    mSavedLayout = (BoringLayout) mLayout;
4311                } else if (mEllipsize != null && boring.width <= w) {
4312                    if (mSavedLayout != null) {
4313                        mLayout = mSavedLayout.
4314                                replaceOrMake(mTransformed, mTextPaint,
4315                                w, alignment, mSpacingMult, mSpacingAdd,
4316                                boring, mIncludePad, mEllipsize,
4317                                ellipsisWidth);
4318                    } else {
4319                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4320                                w, alignment, mSpacingMult, mSpacingAdd,
4321                                boring, mIncludePad, mEllipsize,
4322                                ellipsisWidth);
4323                    }
4324                } else if (mEllipsize != null) {
4325                    mLayout = new StaticLayout(mTransformed,
4326                                0, mTransformed.length(),
4327                                mTextPaint, w, alignment, mSpacingMult,
4328                                mSpacingAdd, mIncludePad, mEllipsize,
4329                                ellipsisWidth);
4330                } else {
4331                    mLayout = new StaticLayout(mTransformed, mTextPaint,
4332                            w, alignment, mSpacingMult, mSpacingAdd,
4333                            mIncludePad);
4334                    // Log.e("aaa", "Boring but wide: " + mTransformed);
4335                }
4336            } else if (mEllipsize != null) {
4337                mLayout = new StaticLayout(mTransformed,
4338                            0, mTransformed.length(),
4339                            mTextPaint, w, alignment, mSpacingMult,
4340                            mSpacingAdd, mIncludePad, mEllipsize,
4341                            ellipsisWidth);
4342            } else {
4343                mLayout = new StaticLayout(mTransformed, mTextPaint,
4344                        w, alignment, mSpacingMult, mSpacingAdd,
4345                        mIncludePad);
4346            }
4347        }
4348
4349        mHintLayout = null;
4350
4351        if (mHint != null) {
4352            if (hintBoring == UNKNOWN_BORING) {
4353                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
4354                                                   mHintBoring);
4355                if (hintBoring != null) {
4356                    mHintBoring = hintBoring;
4357                }
4358            }
4359
4360            if (hintBoring != null) {
4361                if (hintBoring.width <= hintWidth) {
4362                    if (mSavedHintLayout != null) {
4363                        mHintLayout = mSavedHintLayout.
4364                                replaceOrMake(mHint, mTextPaint,
4365                                hintWidth, alignment, mSpacingMult,
4366                                mSpacingAdd, hintBoring, mIncludePad);
4367                    } else {
4368                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
4369                                hintWidth, alignment, mSpacingMult,
4370                                mSpacingAdd, hintBoring, mIncludePad);
4371                    }
4372
4373                    mSavedHintLayout = (BoringLayout) mHintLayout;
4374                } else {
4375                    mHintLayout = new StaticLayout(mHint, mTextPaint,
4376                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
4377                            mIncludePad);
4378                }
4379            } else {
4380                mHintLayout = new StaticLayout(mHint, mTextPaint,
4381                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
4382                        mIncludePad);
4383            }
4384        }
4385
4386        if (bringIntoView) {
4387            registerForPreDraw();
4388        }
4389
4390        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4391            final int height = mLayoutParams.height;
4392            // If the size of the view does not depend on the size of the text, try to
4393            // start the marquee immediately
4394            if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
4395                startMarquee();
4396            } else {
4397                // Defer the start of the marquee until we know our width (see setFrame())
4398                mRestartMarquee = true;
4399            }
4400        }
4401    }
4402
4403    private static int desired(Layout layout) {
4404        int n = layout.getLineCount();
4405        CharSequence text = layout.getText();
4406        float max = 0;
4407
4408        // if any line was wrapped, we can't use it.
4409        // but it's ok for the last line not to have a newline
4410
4411        for (int i = 0; i < n - 1; i++) {
4412            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
4413                return -1;
4414        }
4415
4416        for (int i = 0; i < n; i++) {
4417            max = Math.max(max, layout.getLineWidth(i));
4418        }
4419
4420        return (int) FloatMath.ceil(max);
4421    }
4422
4423    /**
4424     * Set whether the TextView includes extra top and bottom padding to make
4425     * room for accents that go above the normal ascent and descent.
4426     * The default is true.
4427     *
4428     * @attr ref android.R.styleable#TextView_includeFontPadding
4429     */
4430    public void setIncludeFontPadding(boolean includepad) {
4431        mIncludePad = includepad;
4432
4433        if (mLayout != null) {
4434            nullLayouts();
4435            requestLayout();
4436            invalidate();
4437        }
4438    }
4439
4440    private static final BoringLayout.Metrics UNKNOWN_BORING =
4441                                                new BoringLayout.Metrics();
4442
4443    @Override
4444    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
4445        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
4446        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
4447        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
4448        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
4449
4450        int width;
4451        int height;
4452
4453        BoringLayout.Metrics boring = UNKNOWN_BORING;
4454        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
4455
4456        int des = -1;
4457        boolean fromexisting = false;
4458
4459        if (widthMode == MeasureSpec.EXACTLY) {
4460            // Parent has told us how big to be. So be it.
4461            width = widthSize;
4462        } else {
4463            if (mLayout != null && mEllipsize == null) {
4464                des = desired(mLayout);
4465            }
4466
4467            if (des < 0) {
4468                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4469                                               mBoring);
4470                if (boring != null) {
4471                    mBoring = boring;
4472                }
4473            } else {
4474                fromexisting = true;
4475            }
4476
4477            if (boring == null || boring == UNKNOWN_BORING) {
4478                if (des < 0) {
4479                    des = (int) FloatMath.ceil(Layout.
4480                                    getDesiredWidth(mTransformed, mTextPaint));
4481                }
4482
4483                width = des;
4484            } else {
4485                width = boring.width;
4486            }
4487
4488            final Drawables dr = mDrawables;
4489            if (dr != null) {
4490                width = Math.max(width, dr.mDrawableWidthTop);
4491                width = Math.max(width, dr.mDrawableWidthBottom);
4492            }
4493
4494            if (mHint != null) {
4495                int hintDes = -1;
4496                int hintWidth;
4497
4498                if (mHintLayout != null) {
4499                    hintDes = desired(mHintLayout);
4500                }
4501
4502                if (hintDes < 0) {
4503                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
4504                                                       mHintBoring);
4505                    if (hintBoring != null) {
4506                        mHintBoring = hintBoring;
4507                    }
4508                }
4509
4510                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
4511                    if (hintDes < 0) {
4512                        hintDes = (int) FloatMath.ceil(Layout.
4513                                        getDesiredWidth(mHint, mTextPaint));
4514                    }
4515
4516                    hintWidth = hintDes;
4517                } else {
4518                    hintWidth = hintBoring.width;
4519                }
4520
4521                if (hintWidth > width) {
4522                    width = hintWidth;
4523                }
4524            }
4525
4526            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
4527
4528            if (mMaxWidthMode == EMS) {
4529                width = Math.min(width, mMaxWidth * getLineHeight());
4530            } else {
4531                width = Math.min(width, mMaxWidth);
4532            }
4533
4534            if (mMinWidthMode == EMS) {
4535                width = Math.max(width, mMinWidth * getLineHeight());
4536            } else {
4537                width = Math.max(width, mMinWidth);
4538            }
4539
4540            // Check against our minimum width
4541            width = Math.max(width, getSuggestedMinimumWidth());
4542
4543            if (widthMode == MeasureSpec.AT_MOST) {
4544                width = Math.min(widthSize, width);
4545            }
4546        }
4547
4548        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
4549        int unpaddedWidth = want;
4550        int hintWant = want;
4551
4552        if (mHorizontallyScrolling)
4553            want = VERY_WIDE;
4554
4555        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
4556
4557        if (mLayout == null) {
4558            makeNewLayout(want, hintWant, boring, hintBoring,
4559                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
4560                          false);
4561        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
4562                   (mLayout.getEllipsizedWidth() !=
4563                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
4564            if (mHint == null && mEllipsize == null &&
4565                    want > mLayout.getWidth() &&
4566                    (mLayout instanceof BoringLayout ||
4567                        (fromexisting && des >= 0 && des <= want))) {
4568                mLayout.increaseWidthTo(want);
4569            } else {
4570                makeNewLayout(want, hintWant, boring, hintBoring,
4571                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
4572                              false);
4573            }
4574        } else {
4575            // Width has not changed.
4576        }
4577
4578        if (heightMode == MeasureSpec.EXACTLY) {
4579            // Parent has told us how big to be. So be it.
4580            height = heightSize;
4581            mDesiredHeightAtMeasure = -1;
4582        } else {
4583            int desired = getDesiredHeight();
4584
4585            height = desired;
4586            mDesiredHeightAtMeasure = desired;
4587
4588            if (heightMode == MeasureSpec.AT_MOST) {
4589                height = Math.min(desired, height);
4590            }
4591        }
4592
4593        int unpaddedHeight = height - getCompoundPaddingTop() -
4594                                getCompoundPaddingBottom();
4595        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
4596            unpaddedHeight = Math.min(unpaddedHeight,
4597                                      mLayout.getLineTop(mMaximum));
4598        }
4599
4600        /*
4601         * We didn't let makeNewLayout() register to bring the cursor into view,
4602         * so do it here if there is any possibility that it is needed.
4603         */
4604        if (mMovement != null ||
4605            mLayout.getWidth() > unpaddedWidth ||
4606            mLayout.getHeight() > unpaddedHeight) {
4607            registerForPreDraw();
4608        } else {
4609            scrollTo(0, 0);
4610        }
4611
4612        setMeasuredDimension(width, height);
4613    }
4614
4615    private int getDesiredHeight() {
4616        return Math.max(getDesiredHeight(mLayout, true),
4617                        getDesiredHeight(mHintLayout, false));
4618    }
4619
4620    private int getDesiredHeight(Layout layout, boolean cap) {
4621        if (layout == null) {
4622            return 0;
4623        }
4624
4625        int linecount = layout.getLineCount();
4626        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
4627        int desired = layout.getLineTop(linecount);
4628
4629        final Drawables dr = mDrawables;
4630        if (dr != null) {
4631            desired = Math.max(desired, dr.mDrawableHeightLeft);
4632            desired = Math.max(desired, dr.mDrawableHeightRight);
4633        }
4634
4635        desired += pad;
4636
4637        if (mMaxMode == LINES) {
4638            /*
4639             * Don't cap the hint to a certain number of lines.
4640             * (Do cap it, though, if we have a maximum pixel height.)
4641             */
4642            if (cap) {
4643                if (linecount > mMaximum) {
4644                    desired = layout.getLineTop(mMaximum) +
4645                              layout.getBottomPadding();
4646
4647                    if (dr != null) {
4648                        desired = Math.max(desired, dr.mDrawableHeightLeft);
4649                        desired = Math.max(desired, dr.mDrawableHeightRight);
4650                    }
4651
4652                    desired += pad;
4653                    linecount = mMaximum;
4654                }
4655            }
4656        } else {
4657            desired = Math.min(desired, mMaximum);
4658        }
4659
4660        if (mMinMode == LINES) {
4661            if (linecount < mMinimum) {
4662                desired += getLineHeight() * (mMinimum - linecount);
4663            }
4664        } else {
4665            desired = Math.max(desired, mMinimum);
4666        }
4667
4668        // Check against our minimum height
4669        desired = Math.max(desired, getSuggestedMinimumHeight());
4670
4671        return desired;
4672    }
4673
4674    /**
4675     * Check whether a change to the existing text layout requires a
4676     * new view layout.
4677     */
4678    private void checkForResize() {
4679        boolean sizeChanged = false;
4680
4681        if (mLayout != null) {
4682            // Check if our width changed
4683            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
4684                sizeChanged = true;
4685                invalidate();
4686            }
4687
4688            // Check if our height changed
4689            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
4690                int desiredHeight = getDesiredHeight();
4691
4692                if (desiredHeight != this.getHeight()) {
4693                    sizeChanged = true;
4694                }
4695            } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
4696                if (mDesiredHeightAtMeasure >= 0) {
4697                    int desiredHeight = getDesiredHeight();
4698
4699                    if (desiredHeight != mDesiredHeightAtMeasure) {
4700                        sizeChanged = true;
4701                    }
4702                }
4703            }
4704        }
4705
4706        if (sizeChanged) {
4707            requestLayout();
4708            // caller will have already invalidated
4709        }
4710    }
4711
4712    /**
4713     * Check whether entirely new text requires a new view layout
4714     * or merely a new text layout.
4715     */
4716    private void checkForRelayout() {
4717        // If we have a fixed width, we can just swap in a new text layout
4718        // if the text height stays the same or if the view height is fixed.
4719
4720        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
4721                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
4722                (mHint == null || mHintLayout != null) &&
4723                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
4724            // Static width, so try making a new text layout.
4725
4726            int oldht = mLayout.getHeight();
4727            int want = mLayout.getWidth();
4728            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
4729
4730            /*
4731             * No need to bring the text into view, since the size is not
4732             * changing (unless we do the requestLayout(), in which case it
4733             * will happen at measure).
4734             */
4735            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
4736                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
4737
4738            // In a fixed-height view, so use our new text layout.
4739            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
4740                mLayoutParams.height != LayoutParams.FILL_PARENT) {
4741                invalidate();
4742                return;
4743            }
4744
4745            // Dynamic height, but height has stayed the same,
4746            // so use our new text layout.
4747            if (mLayout.getHeight() == oldht &&
4748                (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
4749                invalidate();
4750                return;
4751            }
4752
4753            // We lose: the height has changed and we have a dynamic height.
4754            // Request a new view layout using our new text layout.
4755            requestLayout();
4756            invalidate();
4757        } else {
4758            // Dynamic width, so we have no choice but to request a new
4759            // view layout with a new text layout.
4760
4761            nullLayouts();
4762            requestLayout();
4763            invalidate();
4764        }
4765    }
4766
4767    /**
4768     * Returns true if anything changed.
4769     */
4770    private boolean bringTextIntoView() {
4771        int line = 0;
4772        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
4773            line = mLayout.getLineCount() - 1;
4774        }
4775
4776        Layout.Alignment a = mLayout.getParagraphAlignment(line);
4777        int dir = mLayout.getParagraphDirection(line);
4778        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4779        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
4780        int ht = mLayout.getHeight();
4781
4782        int scrollx, scrolly;
4783
4784        if (a == Layout.Alignment.ALIGN_CENTER) {
4785            /*
4786             * Keep centered if possible, or, if it is too wide to fit,
4787             * keep leading edge in view.
4788             */
4789
4790            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
4791            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
4792
4793            if (right - left < hspace) {
4794                scrollx = (right + left) / 2 - hspace / 2;
4795            } else {
4796                if (dir < 0) {
4797                    scrollx = right - hspace;
4798                } else {
4799                    scrollx = left;
4800                }
4801            }
4802        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
4803            /*
4804             * Keep leading edge in view.
4805             */
4806
4807            if (dir < 0) {
4808                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
4809                scrollx = right - hspace;
4810            } else {
4811                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
4812            }
4813        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
4814            /*
4815             * Keep trailing edge in view.
4816             */
4817
4818            if (dir < 0) {
4819                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
4820            } else {
4821                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
4822                scrollx = right - hspace;
4823            }
4824        }
4825
4826        if (ht < vspace) {
4827            scrolly = 0;
4828        } else {
4829            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
4830                scrolly = ht - vspace;
4831            } else {
4832                scrolly = 0;
4833            }
4834        }
4835
4836        if (scrollx != mScrollX || scrolly != mScrollY) {
4837            scrollTo(scrollx, scrolly);
4838            return true;
4839        } else {
4840            return false;
4841        }
4842    }
4843
4844    /**
4845     * Move the point, specified by the offset, into the view if it is needed.
4846     * This has to be called after layout. Returns true if anything changed.
4847     */
4848    public boolean bringPointIntoView(int offset) {
4849        boolean changed = false;
4850
4851        int line = mLayout.getLineForOffset(offset);
4852
4853        // FIXME: Is it okay to truncate this, or should we round?
4854        final int x = (int)mLayout.getPrimaryHorizontal(offset);
4855        final int top = mLayout.getLineTop(line);
4856        final int bottom = mLayout.getLineTop(line+1);
4857
4858        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
4859        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
4860        int ht = mLayout.getHeight();
4861
4862        int grav;
4863
4864        switch (mLayout.getParagraphAlignment(line)) {
4865            case ALIGN_NORMAL:
4866                grav = 1;
4867                break;
4868
4869            case ALIGN_OPPOSITE:
4870                grav = -1;
4871                break;
4872
4873            default:
4874                grav = 0;
4875        }
4876
4877        grav *= mLayout.getParagraphDirection(line);
4878
4879        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4880        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
4881
4882        int hslack = (bottom - top) / 2;
4883        int vslack = hslack;
4884
4885        if (vslack > vspace / 4)
4886            vslack = vspace / 4;
4887        if (hslack > hspace / 4)
4888            hslack = hspace / 4;
4889
4890        int hs = mScrollX;
4891        int vs = mScrollY;
4892
4893        if (top - vs < vslack)
4894            vs = top - vslack;
4895        if (bottom - vs > vspace - vslack)
4896            vs = bottom - (vspace - vslack);
4897        if (ht - vs < vspace)
4898            vs = ht - vspace;
4899        if (0 - vs > 0)
4900            vs = 0;
4901
4902        if (grav != 0) {
4903            if (x - hs < hslack) {
4904                hs = x - hslack;
4905            }
4906            if (x - hs > hspace - hslack) {
4907                hs = x - (hspace - hslack);
4908            }
4909        }
4910
4911        if (grav < 0) {
4912            if (left - hs > 0)
4913                hs = left;
4914            if (right - hs < hspace)
4915                hs = right - hspace;
4916        } else if (grav > 0) {
4917            if (right - hs < hspace)
4918                hs = right - hspace;
4919            if (left - hs > 0)
4920                hs = left;
4921        } else /* grav == 0 */ {
4922            if (right - left <= hspace) {
4923                /*
4924                 * If the entire text fits, center it exactly.
4925                 */
4926                hs = left - (hspace - (right - left)) / 2;
4927            } else if (x > right - hslack) {
4928                /*
4929                 * If we are near the right edge, keep the right edge
4930                 * at the edge of the view.
4931                 */
4932                hs = right - hspace;
4933            } else if (x < left + hslack) {
4934                /*
4935                 * If we are near the left edge, keep the left edge
4936                 * at the edge of the view.
4937                 */
4938                hs = left;
4939            } else if (left > hs) {
4940                /*
4941                 * Is there whitespace visible at the left?  Fix it if so.
4942                 */
4943                hs = left;
4944            } else if (right < hs + hspace) {
4945                /*
4946                 * Is there whitespace visible at the right?  Fix it if so.
4947                 */
4948                hs = right - hspace;
4949            } else {
4950                /*
4951                 * Otherwise, float as needed.
4952                 */
4953                if (x - hs < hslack) {
4954                    hs = x - hslack;
4955                }
4956                if (x - hs > hspace - hslack) {
4957                    hs = x - (hspace - hslack);
4958                }
4959            }
4960        }
4961
4962        if (hs != mScrollX || vs != mScrollY) {
4963            if (mScroller == null) {
4964                scrollTo(hs, vs);
4965            } else {
4966                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
4967                int dx = hs - mScrollX;
4968                int dy = vs - mScrollY;
4969
4970                if (duration > ANIMATED_SCROLL_GAP) {
4971                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
4972                    invalidate();
4973                } else {
4974                    if (!mScroller.isFinished()) {
4975                        mScroller.abortAnimation();
4976                    }
4977
4978                    scrollBy(dx, dy);
4979                }
4980
4981                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
4982            }
4983
4984            changed = true;
4985        }
4986
4987        if (isFocused()) {
4988            // This offsets because getInterestingRect() is in terms of
4989            // viewport coordinates, but requestRectangleOnScreen()
4990            // is in terms of content coordinates.
4991
4992            Rect r = new Rect();
4993            getInterestingRect(r, x, top, bottom, line);
4994            r.offset(mScrollX, mScrollY);
4995
4996            if (requestRectangleOnScreen(r)) {
4997                changed = true;
4998            }
4999        }
5000
5001        return changed;
5002    }
5003
5004    @Override
5005    public void computeScroll() {
5006        if (mScroller != null) {
5007            if (mScroller.computeScrollOffset()) {
5008                mScrollX = mScroller.getCurrX();
5009                mScrollY = mScroller.getCurrY();
5010                postInvalidate();  // So we draw again
5011            }
5012        }
5013    }
5014
5015    private void getInterestingRect(Rect r, int h, int top, int bottom,
5016                                    int line) {
5017        int paddingTop = getExtendedPaddingTop();
5018        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5019            paddingTop += getVerticalOffset(false);
5020        }
5021        top += paddingTop;
5022        bottom += paddingTop;
5023        h += getCompoundPaddingLeft();
5024
5025        if (line == 0)
5026            top -= getExtendedPaddingTop();
5027        if (line == mLayout.getLineCount() - 1)
5028            bottom += getExtendedPaddingBottom();
5029
5030        r.set(h, top, h+1, bottom);
5031        r.offset(-mScrollX, -mScrollY);
5032    }
5033
5034    @Override
5035    public void debug(int depth) {
5036        super.debug(depth);
5037
5038        String output = debugIndent(depth);
5039        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
5040                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
5041                + "} ";
5042
5043        if (mText != null) {
5044
5045            output += "mText=\"" + mText + "\" ";
5046            if (mLayout != null) {
5047                output += "mLayout width=" + mLayout.getWidth()
5048                        + " height=" + mLayout.getHeight();
5049            }
5050        } else {
5051            output += "mText=NULL";
5052        }
5053        Log.d(VIEW_LOG_TAG, output);
5054    }
5055
5056    /**
5057     * Convenience for {@link Selection#getSelectionStart}.
5058     */
5059    public int getSelectionStart() {
5060        return Selection.getSelectionStart(getText());
5061    }
5062
5063    /**
5064     * Convenience for {@link Selection#getSelectionEnd}.
5065     */
5066    public int getSelectionEnd() {
5067        return Selection.getSelectionEnd(getText());
5068    }
5069
5070    /**
5071     * Return true iff there is a selection inside this text view.
5072     */
5073    public boolean hasSelection() {
5074        return getSelectionStart() != getSelectionEnd();
5075    }
5076
5077    /**
5078     * Sets the properties of this field (lines, horizontally scrolling,
5079     * transformation method) to be for a single-line input.
5080     *
5081     * @attr ref android.R.styleable#TextView_singleLine
5082     */
5083    public void setSingleLine() {
5084        setSingleLine(true);
5085    }
5086
5087    /**
5088     * If true, sets the properties of this field (lines, horizontally
5089     * scrolling, transformation method) to be for a single-line input;
5090     * if false, restores these to the default conditions.
5091     * Note that calling this with false restores default conditions,
5092     * not necessarily those that were in effect prior to calling
5093     * it with true.
5094     *
5095     * @attr ref android.R.styleable#TextView_singleLine
5096     */
5097    public void setSingleLine(boolean singleLine) {
5098        if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
5099                == EditorInfo.TYPE_CLASS_TEXT) {
5100            if (singleLine) {
5101                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5102            } else {
5103                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5104            }
5105        }
5106        applySingleLine(singleLine, true);
5107    }
5108
5109    private void applySingleLine(boolean singleLine, boolean applyTransformation) {
5110        mSingleLine = singleLine;
5111        if (singleLine) {
5112            setLines(1);
5113            setHorizontallyScrolling(true);
5114            if (applyTransformation) {
5115                setTransformationMethod(SingleLineTransformationMethod.
5116                                        getInstance());
5117            }
5118        } else {
5119            setMaxLines(Integer.MAX_VALUE);
5120            setHorizontallyScrolling(false);
5121            if (applyTransformation) {
5122                setTransformationMethod(null);
5123            }
5124        }
5125    }
5126
5127    /**
5128     * Causes words in the text that are longer than the view is wide
5129     * to be ellipsized instead of broken in the middle.  You may also
5130     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
5131     * to constrain the text toa single line.  Use <code>null</code>
5132     * to turn off ellipsizing.
5133     *
5134     * @attr ref android.R.styleable#TextView_ellipsize
5135     */
5136    public void setEllipsize(TextUtils.TruncateAt where) {
5137        mEllipsize = where;
5138
5139        if (mLayout != null) {
5140            nullLayouts();
5141            requestLayout();
5142            invalidate();
5143        }
5144    }
5145
5146    /**
5147     * Sets how many times to repeat the marquee animation. Only applied if the
5148     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
5149     *
5150     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
5151     */
5152    public void setMarqueeRepeatLimit(int marqueeLimit) {
5153        mMarqueeRepeatLimit = marqueeLimit;
5154    }
5155
5156    /**
5157     * Returns where, if anywhere, words that are longer than the view
5158     * is wide should be ellipsized.
5159     */
5160    @ViewDebug.ExportedProperty
5161    public TextUtils.TruncateAt getEllipsize() {
5162        return mEllipsize;
5163    }
5164
5165    /**
5166     * Set the TextView so that when it takes focus, all the text is
5167     * selected.
5168     *
5169     * @attr ref android.R.styleable#TextView_selectAllOnFocus
5170     */
5171    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
5172        mSelectAllOnFocus = selectAllOnFocus;
5173
5174        if (selectAllOnFocus && !(mText instanceof Spannable)) {
5175            setText(mText, BufferType.SPANNABLE);
5176        }
5177    }
5178
5179    /**
5180     * Set whether the cursor is visible.  The default is true.
5181     *
5182     * @attr ref android.R.styleable#TextView_cursorVisible
5183     */
5184    public void setCursorVisible(boolean visible) {
5185        mCursorVisible = visible;
5186        invalidate();
5187
5188        if (visible) {
5189            makeBlink();
5190        } else if (mBlink != null) {
5191            mBlink.removeCallbacks(mBlink);
5192        }
5193    }
5194
5195    private boolean canMarquee() {
5196        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
5197        return width > 0 && mLayout.getLineWidth(0) > width;
5198    }
5199
5200    private void startMarquee() {
5201        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
5202                getLineCount() == 1 && canMarquee()) {
5203            if (mMarquee == null) mMarquee = new Marquee(this);
5204            mMarquee.start(mMarqueeRepeatLimit);
5205        }
5206    }
5207
5208    private void stopMarquee() {
5209        if (mMarquee != null && !mMarquee.isStopped()) {
5210            mMarquee.stop();
5211        }
5212    }
5213
5214    private void startStopMarquee(boolean start) {
5215        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5216            if (start) {
5217                startMarquee();
5218            } else {
5219                stopMarquee();
5220            }
5221        }
5222    }
5223
5224    private static final class Marquee extends Handler {
5225        // TODO: Add an option to configure this
5226        private static final int MARQUEE_DELAY = 1200;
5227        private static final int MARQUEE_RESTART_DELAY = 1200;
5228        private static final int MARQUEE_RESOLUTION = 1000 / 30;
5229        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
5230
5231        private static final byte MARQUEE_STOPPED = 0x0;
5232        private static final byte MARQUEE_STARTING = 0x1;
5233        private static final byte MARQUEE_RUNNING = 0x2;
5234
5235        private static final int MESSAGE_START = 0x1;
5236        private static final int MESSAGE_TICK = 0x2;
5237        private static final int MESSAGE_RESTART = 0x3;
5238
5239        private final WeakReference<TextView> mView;
5240
5241        private byte mStatus = MARQUEE_STOPPED;
5242        private float mScrollUnit;
5243        private float mMaxScroll;
5244        private int mRepeatLimit;
5245
5246        float mScroll;
5247
5248        Marquee(TextView v) {
5249            final float density = v.getContext().getResources().getDisplayMetrics().density;
5250            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
5251            mView = new WeakReference<TextView>(v);
5252        }
5253
5254        @Override
5255        public void handleMessage(Message msg) {
5256            switch (msg.what) {
5257                case MESSAGE_START:
5258                    mStatus = MARQUEE_RUNNING;
5259                    tick();
5260                    break;
5261                case MESSAGE_TICK:
5262                    tick();
5263                    break;
5264                case MESSAGE_RESTART:
5265                    if (mStatus == MARQUEE_RUNNING) {
5266                        if (mRepeatLimit >= 0) {
5267                            mRepeatLimit--;
5268                        }
5269                        start(mRepeatLimit);
5270                    }
5271                    break;
5272            }
5273        }
5274
5275        void tick() {
5276            if (mStatus != MARQUEE_RUNNING) {
5277                return;
5278            }
5279
5280            removeMessages(MESSAGE_TICK);
5281
5282            final TextView textView = mView.get();
5283            if (textView != null && (textView.isFocused() || textView.isSelected())) {
5284                mScroll += mScrollUnit;
5285                if (mScroll > mMaxScroll) {
5286                    mScroll = mMaxScroll;
5287                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
5288                } else {
5289                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
5290                }
5291                textView.invalidate();
5292            }
5293        }
5294
5295        void stop() {
5296            mStatus = MARQUEE_STOPPED;
5297            removeMessages(MESSAGE_START);
5298            removeMessages(MESSAGE_RESTART);
5299            removeMessages(MESSAGE_TICK);
5300            resetScroll();
5301        }
5302
5303        private void resetScroll() {
5304            mScroll = 0.0f;
5305            final TextView textView = mView.get();
5306            if (textView != null) textView.invalidate();
5307        }
5308
5309        void start(int repeatLimit) {
5310            if (repeatLimit == 0) {
5311                stop();
5312                return;
5313            }
5314            mRepeatLimit = repeatLimit;
5315            final TextView textView = mView.get();
5316            if (textView != null && textView.mLayout != null) {
5317                mStatus = MARQUEE_STARTING;
5318                mScroll = 0.0f;
5319                mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() -
5320                        textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight());
5321                textView.invalidate();
5322                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
5323            }
5324        }
5325
5326        boolean isRunning() {
5327            return mStatus == MARQUEE_RUNNING;
5328        }
5329
5330        boolean isStopped() {
5331            return mStatus == MARQUEE_STOPPED;
5332        }
5333    }
5334
5335    /**
5336     * This method is called when the text is changed, in case any
5337     * subclasses would like to know.
5338     *
5339     * @param text The text the TextView is displaying.
5340     * @param start The offset of the start of the range of the text
5341     *              that was modified.
5342     * @param before The offset of the former end of the range of the
5343     *               text that was modified.  If text was simply inserted,
5344     *               this will be the same as <code>start</code>.
5345     *               If text was replaced with new text or deleted, the
5346     *               length of the old text was <code>before-start</code>.
5347     * @param after The offset of the end of the range of the text
5348     *              that was modified.  If text was simply deleted,
5349     *              this will be the same as <code>start</code>.
5350     *              If text was replaced with new text or inserted,
5351     *              the length of the new text is <code>after-start</code>.
5352     */
5353    protected void onTextChanged(CharSequence text,
5354                                 int start, int before, int after) {
5355    }
5356
5357    /**
5358     * This method is called when the selection has changed, in case any
5359     * subclasses would like to know.
5360     *
5361     * @param selStart The new selection start location.
5362     * @param selEnd The new selection end location.
5363     */
5364    protected void onSelectionChanged(int selStart, int selEnd) {
5365    }
5366
5367    /**
5368     * Adds a TextWatcher to the list of those whose methods are called
5369     * whenever this TextView's text changes.
5370     * <p>
5371     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
5372     * not called after {@link #setText} calls.  Now, doing {@link #setText}
5373     * if there are any text changed listeners forces the buffer type to
5374     * Editable if it would not otherwise be and does call this method.
5375     */
5376    public void addTextChangedListener(TextWatcher watcher) {
5377        if (mListeners == null) {
5378            mListeners = new ArrayList<TextWatcher>();
5379        }
5380
5381        mListeners.add(watcher);
5382    }
5383
5384    /**
5385     * Removes the specified TextWatcher from the list of those whose
5386     * methods are called
5387     * whenever this TextView's text changes.
5388     */
5389    public void removeTextChangedListener(TextWatcher watcher) {
5390        if (mListeners != null) {
5391            int i = mListeners.indexOf(watcher);
5392
5393            if (i >= 0) {
5394                mListeners.remove(i);
5395            }
5396        }
5397    }
5398
5399    private void sendBeforeTextChanged(CharSequence text, int start, int before,
5400                                   int after) {
5401        if (mListeners != null) {
5402            final ArrayList<TextWatcher> list = mListeners;
5403            final int count = list.size();
5404            for (int i = 0; i < count; i++) {
5405                list.get(i).beforeTextChanged(text, start, before, after);
5406            }
5407        }
5408    }
5409
5410    /**
5411     * Not private so it can be called from an inner class without going
5412     * through a thunk.
5413     */
5414    void sendOnTextChanged(CharSequence text, int start, int before,
5415                                   int after) {
5416        if (mListeners != null) {
5417            final ArrayList<TextWatcher> list = mListeners;
5418            final int count = list.size();
5419            for (int i = 0; i < count; i++) {
5420                list.get(i).onTextChanged(text, start, before, after);
5421            }
5422        }
5423    }
5424
5425    /**
5426     * Not private so it can be called from an inner class without going
5427     * through a thunk.
5428     */
5429    void sendAfterTextChanged(Editable text) {
5430        if (mListeners != null) {
5431            final ArrayList<TextWatcher> list = mListeners;
5432            final int count = list.size();
5433            for (int i = 0; i < count; i++) {
5434                list.get(i).afterTextChanged(text);
5435            }
5436        }
5437    }
5438
5439    /**
5440     * Not private so it can be called from an inner class without going
5441     * through a thunk.
5442     */
5443    void handleTextChanged(CharSequence buffer, int start,
5444            int before, int after) {
5445        final InputMethodState ims = mInputMethodState;
5446        if (ims == null || ims.mBatchEditNesting == 0) {
5447            updateAfterEdit();
5448        }
5449        if (ims != null) {
5450            ims.mContentChanged = true;
5451            if (ims.mChangedStart < 0) {
5452                ims.mChangedStart = start;
5453                ims.mChangedEnd = start+before;
5454            } else {
5455                if (ims.mChangedStart > start) ims.mChangedStart = start;
5456                if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
5457            }
5458            ims.mChangedDelta += after-before;
5459        }
5460
5461        sendOnTextChanged(buffer, start, before, after);
5462        onTextChanged(buffer, start, before, after);
5463    }
5464
5465    /**
5466     * Not private so it can be called from an inner class without going
5467     * through a thunk.
5468     */
5469    void spanChange(Spanned buf, Object what, int oldStart, int newStart,
5470            int oldEnd, int newEnd) {
5471        // XXX Make the start and end move together if this ends up
5472        // spending too much time invalidating.
5473
5474        boolean selChanged = false;
5475        int newSelStart=-1, newSelEnd=-1;
5476
5477        final InputMethodState ims = mInputMethodState;
5478
5479        if (what == Selection.SELECTION_END) {
5480            mHighlightPathBogus = true;
5481            selChanged = true;
5482            newSelEnd = newStart;
5483
5484            if (!isFocused()) {
5485                mSelectionMoved = true;
5486            }
5487
5488            if (oldStart >= 0 || newStart >= 0) {
5489                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
5490                registerForPreDraw();
5491
5492                if (isFocused()) {
5493                    mShowCursor = SystemClock.uptimeMillis();
5494                    makeBlink();
5495                }
5496            }
5497        }
5498
5499        if (what == Selection.SELECTION_START) {
5500            mHighlightPathBogus = true;
5501            selChanged = true;
5502            newSelStart = newStart;
5503
5504            if (!isFocused()) {
5505                mSelectionMoved = true;
5506            }
5507
5508            if (oldStart >= 0 || newStart >= 0) {
5509                int end = Selection.getSelectionEnd(buf);
5510                invalidateCursor(end, oldStart, newStart);
5511            }
5512        }
5513
5514        if (selChanged) {
5515            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
5516                if (newSelStart < 0) {
5517                    newSelStart = Selection.getSelectionStart(buf);
5518                }
5519                if (newSelEnd < 0) {
5520                    newSelEnd = Selection.getSelectionEnd(buf);
5521                }
5522                onSelectionChanged(newSelStart, newSelEnd);
5523            }
5524        }
5525
5526        if (what instanceof UpdateAppearance ||
5527            what instanceof ParagraphStyle) {
5528            if (ims == null || ims.mBatchEditNesting == 0) {
5529                invalidate();
5530                mHighlightPathBogus = true;
5531                checkForResize();
5532            } else {
5533                ims.mContentChanged = true;
5534            }
5535        }
5536
5537        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
5538            mHighlightPathBogus = true;
5539
5540            if (Selection.getSelectionStart(buf) >= 0) {
5541                if (ims == null || ims.mBatchEditNesting == 0) {
5542                    invalidateCursor();
5543                } else {
5544                    ims.mCursorChanged = true;
5545                }
5546            }
5547        }
5548
5549        if (what instanceof ParcelableSpan) {
5550            // If this is a span that can be sent to a remote process,
5551            // the current extract editor would be interested in it.
5552            if (ims != null && ims.mExtracting != null) {
5553                if (ims.mBatchEditNesting != 0) {
5554                    if (oldStart >= 0) {
5555                        if (ims.mChangedStart > oldStart) {
5556                            ims.mChangedStart = oldStart;
5557                        }
5558                        if (ims.mChangedStart > oldEnd) {
5559                            ims.mChangedStart = oldEnd;
5560                        }
5561                    }
5562                    if (newStart >= 0) {
5563                        if (ims.mChangedStart > newStart) {
5564                            ims.mChangedStart = newStart;
5565                        }
5566                        if (ims.mChangedStart > newEnd) {
5567                            ims.mChangedStart = newEnd;
5568                        }
5569                    }
5570                } else {
5571                    if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
5572                            + oldStart + "-" + oldEnd + ","
5573                            + newStart + "-" + newEnd + what);
5574                    ims.mContentChanged = true;
5575                }
5576            }
5577        }
5578    }
5579
5580    private class ChangeWatcher
5581    implements TextWatcher, SpanWatcher {
5582        public void beforeTextChanged(CharSequence buffer, int start,
5583                                      int before, int after) {
5584            if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
5585                    + " before=" + before + " after=" + after + ": " + buffer);
5586            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
5587        }
5588
5589        public void onTextChanged(CharSequence buffer, int start,
5590                                  int before, int after) {
5591            if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
5592                    + " before=" + before + " after=" + after + ": " + buffer);
5593            TextView.this.handleTextChanged(buffer, start, before, after);
5594        }
5595
5596        public void afterTextChanged(Editable buffer) {
5597            if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
5598            TextView.this.sendAfterTextChanged(buffer);
5599
5600            if (MetaKeyKeyListener.getMetaState(buffer,
5601                                 MetaKeyKeyListener.META_SELECTING) != 0) {
5602                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
5603            }
5604        }
5605
5606        public void onSpanChanged(Spannable buf,
5607                                  Object what, int s, int e, int st, int en) {
5608            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
5609                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
5610            TextView.this.spanChange(buf, what, s, st, e, en);
5611        }
5612
5613        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
5614            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
5615                    + " what=" + what + ": " + buf);
5616            TextView.this.spanChange(buf, what, -1, s, -1, e);
5617        }
5618
5619        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
5620            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
5621                    + " what=" + what + ": " + buf);
5622            TextView.this.spanChange(buf, what, s, -1, e, -1);
5623        }
5624    }
5625
5626    private void makeBlink() {
5627        if (!mCursorVisible) {
5628            if (mBlink != null) {
5629                mBlink.removeCallbacks(mBlink);
5630            }
5631
5632            return;
5633        }
5634
5635        if (mBlink == null)
5636            mBlink = new Blink(this);
5637
5638        mBlink.removeCallbacks(mBlink);
5639        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
5640    }
5641
5642    @Override
5643    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
5644        mShowCursor = SystemClock.uptimeMillis();
5645
5646        ensureEndedBatchEdit();
5647
5648        if (focused) {
5649            int selStart = getSelectionStart();
5650            int selEnd = getSelectionEnd();
5651
5652            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
5653                boolean selMoved = mSelectionMoved;
5654
5655                if (mMovement != null) {
5656                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
5657                }
5658
5659                if (mSelectAllOnFocus) {
5660                    Selection.setSelection((Spannable) mText, 0, mText.length());
5661                }
5662
5663                if (selMoved && selStart >= 0 && selEnd >= 0) {
5664                    /*
5665                     * Someone intentionally set the selection, so let them
5666                     * do whatever it is that they wanted to do instead of
5667                     * the default on-focus behavior.  We reset the selection
5668                     * here instead of just skipping the onTakeFocus() call
5669                     * because some movement methods do something other than
5670                     * just setting the selection in theirs and we still
5671                     * need to go through that path.
5672                     */
5673
5674                    Selection.setSelection((Spannable) mText, selStart, selEnd);
5675                }
5676            }
5677
5678            mFrozenWithFocus = false;
5679            mSelectionMoved = false;
5680
5681            if (mText instanceof Spannable) {
5682                Spannable sp = (Spannable) mText;
5683                MetaKeyKeyListener.resetMetaState(sp);
5684            }
5685
5686            makeBlink();
5687
5688            if (mError != null) {
5689                showError();
5690            }
5691        } else {
5692            if (mError != null) {
5693                hideError();
5694            }
5695            // Don't leave us in the middle of a batch edit.
5696            onEndBatchEdit();
5697        }
5698
5699        startStopMarquee(focused);
5700
5701        if (mTransformation != null) {
5702            mTransformation.onFocusChanged(this, mText, focused, direction,
5703                                           previouslyFocusedRect);
5704        }
5705
5706        super.onFocusChanged(focused, direction, previouslyFocusedRect);
5707    }
5708
5709    @Override
5710    public void onWindowFocusChanged(boolean hasWindowFocus) {
5711        super.onWindowFocusChanged(hasWindowFocus);
5712
5713        if (hasWindowFocus) {
5714            if (mBlink != null) {
5715                mBlink.uncancel();
5716
5717                if (isFocused()) {
5718                    mShowCursor = SystemClock.uptimeMillis();
5719                    makeBlink();
5720                }
5721            }
5722        } else {
5723            if (mBlink != null) {
5724                mBlink.cancel();
5725            }
5726            // Don't leave us in the middle of a batch edit.
5727            onEndBatchEdit();
5728        }
5729
5730        startStopMarquee(hasWindowFocus);
5731    }
5732
5733    @Override
5734    public void setSelected(boolean selected) {
5735        boolean wasSelected = isSelected();
5736
5737        super.setSelected(selected);
5738
5739        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5740            if (selected) {
5741                startMarquee();
5742            } else {
5743                stopMarquee();
5744            }
5745        }
5746    }
5747
5748    @Override
5749    public boolean onTouchEvent(MotionEvent event) {
5750        final boolean superResult = super.onTouchEvent(event);
5751
5752        final int action = event.getAction();
5753
5754        /*
5755         * Don't handle the release after a long press, because it will
5756         * move the selection away from whatever the menu action was
5757         * trying to affect.
5758         */
5759        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
5760            mEatTouchRelease = false;
5761            return superResult;
5762        }
5763
5764        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
5765
5766            if (action == MotionEvent.ACTION_DOWN) {
5767                mScrolled = false;
5768            }
5769
5770            boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event);
5771
5772            if (mText instanceof Editable && onCheckIsTextEditor()) {
5773                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
5774                    InputMethodManager imm = (InputMethodManager)
5775                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
5776                    imm.showSoftInput(this, 0);
5777                }
5778            }
5779
5780            if (moved) {
5781                return true;
5782            }
5783        }
5784
5785        return superResult;
5786    }
5787
5788    @Override
5789    public void cancelLongPress() {
5790        super.cancelLongPress();
5791        mScrolled = true;
5792    }
5793
5794    @Override
5795    public boolean onTrackballEvent(MotionEvent event) {
5796        if (mMovement != null && mText instanceof Spannable &&
5797            mLayout != null) {
5798            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
5799                return true;
5800            }
5801        }
5802
5803        return super.onTrackballEvent(event);
5804    }
5805
5806    public void setScroller(Scroller s) {
5807        mScroller = s;
5808    }
5809
5810    private static class Blink extends Handler
5811            implements Runnable {
5812        private WeakReference<TextView> mView;
5813        private boolean mCancelled;
5814
5815        public Blink(TextView v) {
5816            mView = new WeakReference<TextView>(v);
5817        }
5818
5819        public void run() {
5820            if (mCancelled) {
5821                return;
5822            }
5823
5824            removeCallbacks(Blink.this);
5825
5826            TextView tv = mView.get();
5827
5828            if (tv != null && tv.isFocused()) {
5829                int st = Selection.getSelectionStart(tv.mText);
5830                int en = Selection.getSelectionEnd(tv.mText);
5831
5832                if (st == en && st >= 0 && en >= 0) {
5833                    if (tv.mLayout != null) {
5834                        tv.invalidateCursorPath();
5835                    }
5836
5837                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
5838                }
5839            }
5840        }
5841
5842        void cancel() {
5843            if (!mCancelled) {
5844                removeCallbacks(Blink.this);
5845                mCancelled = true;
5846            }
5847        }
5848
5849        void uncancel() {
5850            mCancelled = false;
5851        }
5852    }
5853
5854    @Override
5855    protected float getLeftFadingEdgeStrength() {
5856        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5857            if (mMarquee != null && !mMarquee.isStopped()) {
5858                final Marquee marquee = mMarquee;
5859                return marquee.mScroll / getHorizontalFadingEdgeLength();
5860            } else if (getLineCount() == 1) {
5861                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
5862                    case Gravity.LEFT:
5863                        return 0.0f;
5864                    case Gravity.RIGHT:
5865                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
5866                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
5867                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
5868                    case Gravity.CENTER_HORIZONTAL:
5869                        return 0.0f;
5870                }
5871            }
5872        }
5873        return super.getLeftFadingEdgeStrength();
5874    }
5875
5876    @Override
5877    protected float getRightFadingEdgeStrength() {
5878        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5879            if (mMarquee != null && !mMarquee.isStopped()) {
5880                final Marquee marquee = mMarquee;
5881                return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
5882            } else if (getLineCount() == 1) {
5883                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
5884                    case Gravity.LEFT:
5885                        return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) -
5886                                getCompoundPaddingLeft() - getCompoundPaddingRight()) /
5887                                getHorizontalFadingEdgeLength();
5888                    case Gravity.RIGHT:
5889                        return 0.0f;
5890                    case Gravity.CENTER_HORIZONTAL:
5891                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
5892                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
5893                                getHorizontalFadingEdgeLength();
5894                }
5895            }
5896        }
5897        return super.getRightFadingEdgeStrength();
5898    }
5899
5900    @Override
5901    protected int computeHorizontalScrollRange() {
5902        if (mLayout != null)
5903            return mLayout.getWidth();
5904
5905        return super.computeHorizontalScrollRange();
5906    }
5907
5908    @Override
5909    protected int computeVerticalScrollRange() {
5910        if (mLayout != null)
5911            return mLayout.getHeight();
5912
5913        return super.computeVerticalScrollRange();
5914    }
5915
5916    public enum BufferType {
5917        NORMAL, SPANNABLE, EDITABLE,
5918    }
5919
5920    /**
5921     * Returns the TextView_textColor attribute from the
5922     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
5923     * from the TextView_textAppearance attribute, if TextView_textColor
5924     * was not set directly.
5925     */
5926    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
5927        ColorStateList colors;
5928        colors = attrs.getColorStateList(com.android.internal.R.styleable.
5929                                         TextView_textColor);
5930
5931        if (colors == null) {
5932            int ap = attrs.getResourceId(com.android.internal.R.styleable.
5933                                         TextView_textAppearance, -1);
5934            if (ap != -1) {
5935                TypedArray appearance;
5936                appearance = context.obtainStyledAttributes(ap,
5937                                            com.android.internal.R.styleable.TextAppearance);
5938                colors = appearance.getColorStateList(com.android.internal.R.styleable.
5939                                                  TextAppearance_textColor);
5940                appearance.recycle();
5941            }
5942        }
5943
5944        return colors;
5945    }
5946
5947    /**
5948     * Returns the default color from the TextView_textColor attribute
5949     * from the AttributeSet, if set, or the default color from the
5950     * TextAppearance_textColor from the TextView_textAppearance attribute,
5951     * if TextView_textColor was not set directly.
5952     */
5953    public static int getTextColor(Context context,
5954                                   TypedArray attrs,
5955                                   int def) {
5956        ColorStateList colors = getTextColors(context, attrs);
5957
5958        if (colors == null) {
5959            return def;
5960        } else {
5961            return colors.getDefaultColor();
5962        }
5963    }
5964
5965    @Override
5966    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
5967        switch (keyCode) {
5968        case KeyEvent.KEYCODE_A:
5969            if (canSelectAll()) {
5970                return onTextContextMenuItem(ID_SELECT_ALL);
5971            }
5972
5973            break;
5974
5975        case KeyEvent.KEYCODE_X:
5976            if (canCut()) {
5977                return onTextContextMenuItem(ID_CUT);
5978            }
5979
5980            break;
5981
5982        case KeyEvent.KEYCODE_C:
5983            if (canCopy()) {
5984                return onTextContextMenuItem(ID_COPY);
5985            }
5986
5987            break;
5988
5989        case KeyEvent.KEYCODE_V:
5990            if (canPaste()) {
5991                return onTextContextMenuItem(ID_PASTE);
5992            }
5993
5994            break;
5995        }
5996
5997        return super.onKeyShortcut(keyCode, event);
5998    }
5999
6000    private boolean canSelectAll() {
6001        if (mText instanceof Spannable && mText.length() != 0 &&
6002            mMovement != null && mMovement.canSelectArbitrarily()) {
6003            return true;
6004        }
6005
6006        return false;
6007    }
6008
6009    private boolean canSelectText() {
6010        if (mText instanceof Spannable && mText.length() != 0 &&
6011            mMovement != null && mMovement.canSelectArbitrarily()) {
6012            return true;
6013        }
6014
6015        return false;
6016    }
6017
6018    private boolean canCut() {
6019        if (mTransformation instanceof PasswordTransformationMethod) {
6020            return false;
6021        }
6022
6023        if (mText.length() > 0 && getSelectionStart() >= 0) {
6024            if (mText instanceof Editable && mInput != null) {
6025                return true;
6026            }
6027        }
6028
6029        return false;
6030    }
6031
6032    private boolean canCopy() {
6033        if (mTransformation instanceof PasswordTransformationMethod) {
6034            return false;
6035        }
6036
6037        if (mText.length() > 0 && getSelectionStart() >= 0) {
6038            return true;
6039        }
6040
6041        return false;
6042    }
6043
6044    private boolean canPaste() {
6045        if (mText instanceof Editable && mInput != null &&
6046            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
6047            ClipboardManager clip = (ClipboardManager)getContext()
6048                    .getSystemService(Context.CLIPBOARD_SERVICE);
6049            if (clip.hasText()) {
6050                return true;
6051            }
6052        }
6053
6054        return false;
6055    }
6056
6057    /**
6058     * Returns a word to add to the dictionary from the context menu,
6059     * or null if there is no cursor or no word at the cursor.
6060     */
6061    private String getWordForDictionary() {
6062        int end = getSelectionEnd();
6063
6064        if (end < 0) {
6065            return null;
6066        }
6067
6068        int start = end;
6069        char c;
6070        int len = mText.length();
6071
6072        while (start > 0 && (((c = mTransformed.charAt(start - 1)) == '\'') ||
6073                             (Character.isLetterOrDigit(c)))) {
6074            start--;
6075        }
6076
6077        while (end < len && (((c = mTransformed.charAt(end)) == '\'') ||
6078                             (Character.isLetterOrDigit(c)))) {
6079            end++;
6080        }
6081
6082        if (start == end) {
6083            return null;
6084        }
6085
6086        return TextUtils.substring(mTransformed, start, end);
6087    }
6088
6089    @Override
6090    protected void onCreateContextMenu(ContextMenu menu) {
6091        super.onCreateContextMenu(menu);
6092        boolean added = false;
6093
6094        if (!isFocused()) {
6095            if (isFocusable() && mInput != null) {
6096                if (canCopy()) {
6097                    MenuHandler handler = new MenuHandler();
6098                    int name = com.android.internal.R.string.copyAll;
6099
6100                    menu.add(0, ID_COPY, 0, name).
6101                        setOnMenuItemClickListener(handler).
6102                        setAlphabeticShortcut('c');
6103                    menu.setHeaderTitle(com.android.internal.R.string.
6104                        editTextMenuTitle);
6105                }
6106            }
6107
6108            return;
6109        }
6110
6111        MenuHandler handler = new MenuHandler();
6112
6113        if (canSelectAll()) {
6114            menu.add(0, ID_SELECT_ALL, 0,
6115                    com.android.internal.R.string.selectAll).
6116                setOnMenuItemClickListener(handler).
6117                setAlphabeticShortcut('a');
6118            added = true;
6119        }
6120
6121        boolean selection = getSelectionStart() != getSelectionEnd();
6122
6123        if (canSelectText()) {
6124            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
6125                menu.add(0, ID_STOP_SELECTING_TEXT, 0,
6126                        com.android.internal.R.string.stopSelectingText).
6127                    setOnMenuItemClickListener(handler);
6128                added = true;
6129            } else {
6130                menu.add(0, ID_START_SELECTING_TEXT, 0,
6131                        com.android.internal.R.string.selectText).
6132                    setOnMenuItemClickListener(handler);
6133                added = true;
6134            }
6135        }
6136
6137        if (canCut()) {
6138            int name;
6139            if (selection) {
6140                name = com.android.internal.R.string.cut;
6141            } else {
6142                name = com.android.internal.R.string.cutAll;
6143            }
6144
6145            menu.add(0, ID_CUT, 0, name).
6146                setOnMenuItemClickListener(handler).
6147                setAlphabeticShortcut('x');
6148            added = true;
6149        }
6150
6151        if (canCopy()) {
6152            int name;
6153            if (selection) {
6154                name = com.android.internal.R.string.copy;
6155            } else {
6156                name = com.android.internal.R.string.copyAll;
6157            }
6158
6159            menu.add(0, ID_COPY, 0, name).
6160                setOnMenuItemClickListener(handler).
6161                setAlphabeticShortcut('c');
6162            added = true;
6163        }
6164
6165        if (canPaste()) {
6166            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
6167                    setOnMenuItemClickListener(handler).
6168                    setAlphabeticShortcut('v');
6169            added = true;
6170        }
6171
6172        if (mText instanceof Spanned) {
6173            int selStart = getSelectionStart();
6174            int selEnd = getSelectionEnd();
6175
6176            int min = Math.min(selStart, selEnd);
6177            int max = Math.max(selStart, selEnd);
6178
6179            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
6180                                                        URLSpan.class);
6181            if (urls.length == 1) {
6182                menu.add(0, ID_COPY_URL, 0,
6183                         com.android.internal.R.string.copyUrl).
6184                            setOnMenuItemClickListener(handler);
6185                added = true;
6186            }
6187        }
6188
6189        if (isInputMethodTarget()) {
6190            menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
6191                    setOnMenuItemClickListener(handler);
6192            added = true;
6193        }
6194
6195        String word = getWordForDictionary();
6196        if (word != null) {
6197            menu.add(1, ID_ADD_TO_DICTIONARY, 0,
6198                     getContext().getString(com.android.internal.R.string.addToDictionary, word)).
6199                    setOnMenuItemClickListener(handler);
6200            added = true;
6201
6202        }
6203
6204        if (added) {
6205            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
6206        }
6207    }
6208
6209    /**
6210     * Returns whether this text view is a current input method target.  The
6211     * default implementation just checks with {@link InputMethodManager}.
6212     */
6213    public boolean isInputMethodTarget() {
6214        InputMethodManager imm = InputMethodManager.peekInstance();
6215        return imm != null && imm.isActive(this);
6216    }
6217
6218    private static final int ID_SELECT_ALL = android.R.id.selectAll;
6219    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
6220    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
6221    private static final int ID_CUT = android.R.id.cut;
6222    private static final int ID_COPY = android.R.id.copy;
6223    private static final int ID_PASTE = android.R.id.paste;
6224    private static final int ID_COPY_URL = android.R.id.copyUrl;
6225    private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
6226    private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
6227
6228    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
6229        public boolean onMenuItemClick(MenuItem item) {
6230            return onTextContextMenuItem(item.getItemId());
6231        }
6232    }
6233
6234    /**
6235     * Called when a context menu option for the text view is selected.  Currently
6236     * this will be one of: {@link android.R.id#selectAll},
6237     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
6238     * {@link android.R.id#cut}, {@link android.R.id#copy},
6239     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
6240     * or {@link android.R.id#switchInputMethod}.
6241     */
6242    public boolean onTextContextMenuItem(int id) {
6243        int selStart = getSelectionStart();
6244        int selEnd = getSelectionEnd();
6245
6246        if (!isFocused()) {
6247            selStart = 0;
6248            selEnd = mText.length();
6249        }
6250
6251        int min = Math.min(selStart, selEnd);
6252        int max = Math.max(selStart, selEnd);
6253
6254        if (min < 0) {
6255            min = 0;
6256        }
6257        if (max < 0) {
6258            max = 0;
6259        }
6260
6261        ClipboardManager clip = (ClipboardManager)getContext()
6262                .getSystemService(Context.CLIPBOARD_SERVICE);
6263
6264        switch (id) {
6265            case ID_SELECT_ALL:
6266                Selection.setSelection((Spannable) mText, 0,
6267                        mText.length());
6268                return true;
6269
6270            case ID_START_SELECTING_TEXT:
6271                MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
6272                return true;
6273
6274            case ID_STOP_SELECTING_TEXT:
6275                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6276                Selection.setSelection((Spannable) mText, getSelectionEnd());
6277                return true;
6278
6279            case ID_CUT:
6280                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6281
6282                if (min == max) {
6283                    min = 0;
6284                    max = mText.length();
6285                }
6286
6287                clip.setText(mTransformed.subSequence(min, max));
6288                ((Editable) mText).delete(min, max);
6289                return true;
6290
6291            case ID_COPY:
6292                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6293
6294                if (min == max) {
6295                    min = 0;
6296                    max = mText.length();
6297                }
6298
6299                clip.setText(mTransformed.subSequence(min, max));
6300                return true;
6301
6302            case ID_PASTE:
6303                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6304
6305                CharSequence paste = clip.getText();
6306
6307                if (paste != null) {
6308                    Selection.setSelection((Spannable) mText, max);
6309                    ((Editable) mText).replace(min, max, paste);
6310                }
6311
6312                return true;
6313
6314            case ID_COPY_URL:
6315                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6316
6317                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
6318                                                       URLSpan.class);
6319                if (urls.length == 1) {
6320                    clip.setText(urls[0].getURL());
6321                }
6322
6323                return true;
6324
6325            case ID_SWITCH_INPUT_METHOD:
6326                InputMethodManager imm = InputMethodManager.peekInstance();
6327                if (imm != null) {
6328                    imm.showInputMethodPicker();
6329                }
6330                return true;
6331
6332            case ID_ADD_TO_DICTIONARY:
6333                String word = getWordForDictionary();
6334
6335                if (word != null) {
6336                    Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
6337                    i.putExtra("word", word);
6338                    getContext().startActivity(i);
6339                }
6340
6341                return true;
6342            }
6343
6344        return false;
6345    }
6346
6347    public boolean performLongClick() {
6348        if (super.performLongClick()) {
6349            mEatTouchRelease = true;
6350            return true;
6351        }
6352
6353        return false;
6354    }
6355
6356    @ViewDebug.ExportedProperty
6357    private CharSequence            mText;
6358    private CharSequence            mTransformed;
6359    private BufferType              mBufferType = BufferType.NORMAL;
6360
6361    private int                     mInputType = EditorInfo.TYPE_NULL;
6362    private CharSequence            mHint;
6363    private Layout                  mHintLayout;
6364
6365    private KeyListener             mInput;
6366
6367    private MovementMethod          mMovement;
6368    private TransformationMethod    mTransformation;
6369    private ChangeWatcher           mChangeWatcher;
6370
6371    private ArrayList<TextWatcher>  mListeners = null;
6372
6373    // display attributes
6374    private TextPaint mTextPaint;
6375    private Paint                   mHighlightPaint;
6376    private int                     mHighlightColor = 0xFFBBDDFF;
6377    private Layout                  mLayout;
6378
6379    private long                    mShowCursor;
6380    private Blink                   mBlink;
6381    private boolean                 mCursorVisible = true;
6382
6383    private boolean                 mSelectAllOnFocus = false;
6384
6385    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
6386    private boolean                 mHorizontallyScrolling;
6387
6388    private int                     mAutoLinkMask;
6389    private boolean                 mLinksClickable = true;
6390
6391    private float                   mSpacingMult = 1;
6392    private float                   mSpacingAdd = 0;
6393
6394    private static final int        LINES = 1;
6395    private static final int        EMS = LINES;
6396    private static final int        PIXELS = 2;
6397
6398    private int                     mMaximum = Integer.MAX_VALUE;
6399    private int                     mMaxMode = LINES;
6400    private int                     mMinimum = 0;
6401    private int                     mMinMode = LINES;
6402
6403    private int                     mMaxWidth = Integer.MAX_VALUE;
6404    private int                     mMaxWidthMode = PIXELS;
6405    private int                     mMinWidth = 0;
6406    private int                     mMinWidthMode = PIXELS;
6407
6408    private boolean                 mSingleLine;
6409    private int                     mDesiredHeightAtMeasure = -1;
6410    private boolean                 mIncludePad = true;
6411
6412    // tmp primitives, so we don't alloc them on each draw
6413    private Path                    mHighlightPath;
6414    private boolean                 mHighlightPathBogus = true;
6415    private static final RectF      sTempRect = new RectF();
6416
6417    // XXX should be much larger
6418    private static final int        VERY_WIDE = 16384;
6419
6420    private static final int        BLINK = 500;
6421
6422    private static final int ANIMATED_SCROLL_GAP = 250;
6423    private long mLastScroll;
6424    private Scroller mScroller = null;
6425
6426    private BoringLayout.Metrics mBoring;
6427    private BoringLayout.Metrics mHintBoring;
6428
6429    private BoringLayout mSavedLayout, mSavedHintLayout;
6430
6431    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
6432    private InputFilter[] mFilters = NO_FILTERS;
6433    private static final Spanned EMPTY_SPANNED = new SpannedString("");
6434}
6435