TextView.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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.res.ColorStateList;
21import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Path;
26import android.graphics.Rect;
27import android.graphics.RectF;
28import android.graphics.Typeface;
29import android.graphics.drawable.Drawable;
30import android.os.Handler;
31import android.os.Parcel;
32import android.os.Parcelable;
33import android.os.SystemClock;
34import android.text.BoringLayout;
35import android.text.DynamicLayout;
36import android.text.Editable;
37import android.text.GetChars;
38import android.text.GraphicsOperations;
39import android.text.ClipboardManager;
40import android.text.InputFilter;
41import android.text.Layout;
42import android.text.Selection;
43import android.text.SpanWatcher;
44import android.text.Spannable;
45import android.text.Spanned;
46import android.text.SpannedString;
47import android.text.SpannableString;
48import android.text.StaticLayout;
49import android.text.TextPaint;
50import android.text.TextUtils;
51import android.text.TextWatcher;
52import android.text.method.DialerKeyListener;
53import android.text.method.DigitsKeyListener;
54import android.text.method.KeyListener;
55import android.text.method.LinkMovementMethod;
56import android.text.method.MetaKeyKeyListener;
57import android.text.method.MovementMethod;
58import android.text.method.PasswordTransformationMethod;
59import android.text.method.SingleLineTransformationMethod;
60import android.text.method.TextKeyListener;
61import android.text.method.TransformationMethod;
62import android.text.style.ParagraphStyle;
63import android.text.style.URLSpan;
64import android.text.style.UpdateLayout;
65import android.text.util.Linkify;
66import android.util.AttributeSet;
67import android.util.Log;
68import android.util.FloatMath;
69import android.util.TypedValue;
70import android.view.ContextMenu;
71import android.view.Gravity;
72import android.view.KeyEvent;
73import android.view.LayoutInflater;
74import android.view.MenuItem;
75import android.view.MotionEvent;
76import android.view.View;
77import android.view.ViewDebug;
78import android.view.ViewTreeObserver;
79import android.view.ViewGroup.LayoutParams;
80import android.view.animation.AnimationUtils;
81import android.widget.RemoteViews.RemoteView;
82
83import java.lang.ref.WeakReference;
84import java.util.ArrayList;
85
86import com.android.internal.util.FastMath;
87
88/**
89 * Displays text to the user and optionally allows them to edit it.  A TextView
90 * is a complete text editor, however the basic class is configured to not
91 * allow editing; see {@link EditText} for a subclass that configures the text
92 * view for editing.
93 *
94 * <p>
95 * <b>XML attributes</b>
96 * <p>
97 * See {@link android.R.styleable#TextView TextView Attributes},
98 * {@link android.R.styleable#View View Attributes}
99 *
100 * @attr ref android.R.styleable#TextView_text
101 * @attr ref android.R.styleable#TextView_bufferType
102 * @attr ref android.R.styleable#TextView_hint
103 * @attr ref android.R.styleable#TextView_textColor
104 * @attr ref android.R.styleable#TextView_textColorHighlight
105 * @attr ref android.R.styleable#TextView_textColorHint
106 * @attr ref android.R.styleable#TextView_textSize
107 * @attr ref android.R.styleable#TextView_textScaleX
108 * @attr ref android.R.styleable#TextView_typeface
109 * @attr ref android.R.styleable#TextView_textStyle
110 * @attr ref android.R.styleable#TextView_cursorVisible
111 * @attr ref android.R.styleable#TextView_maxLines
112 * @attr ref android.R.styleable#TextView_maxHeight
113 * @attr ref android.R.styleable#TextView_lines
114 * @attr ref android.R.styleable#TextView_height
115 * @attr ref android.R.styleable#TextView_minLines
116 * @attr ref android.R.styleable#TextView_minHeight
117 * @attr ref android.R.styleable#TextView_maxEms
118 * @attr ref android.R.styleable#TextView_maxWidth
119 * @attr ref android.R.styleable#TextView_ems
120 * @attr ref android.R.styleable#TextView_width
121 * @attr ref android.R.styleable#TextView_minEms
122 * @attr ref android.R.styleable#TextView_minWidth
123 * @attr ref android.R.styleable#TextView_gravity
124 * @attr ref android.R.styleable#TextView_scrollHorizontally
125 * @attr ref android.R.styleable#TextView_password
126 * @attr ref android.R.styleable#TextView_singleLine
127 * @attr ref android.R.styleable#TextView_selectAllOnFocus
128 * @attr ref android.R.styleable#TextView_includeFontPadding
129 * @attr ref android.R.styleable#TextView_maxLength
130 * @attr ref android.R.styleable#TextView_shadowColor
131 * @attr ref android.R.styleable#TextView_shadowDx
132 * @attr ref android.R.styleable#TextView_shadowDy
133 * @attr ref android.R.styleable#TextView_shadowRadius
134 * @attr ref android.R.styleable#TextView_autoLink
135 * @attr ref android.R.styleable#TextView_linksClickable
136 * @attr ref android.R.styleable#TextView_numeric
137 * @attr ref android.R.styleable#TextView_digits
138 * @attr ref android.R.styleable#TextView_phoneNumber
139 * @attr ref android.R.styleable#TextView_inputMethod
140 * @attr ref android.R.styleable#TextView_capitalize
141 * @attr ref android.R.styleable#TextView_autoText
142 * @attr ref android.R.styleable#TextView_editable
143 * @attr ref android.R.styleable#TextView_drawableTop
144 * @attr ref android.R.styleable#TextView_drawableBottom
145 * @attr ref android.R.styleable#TextView_drawableRight
146 * @attr ref android.R.styleable#TextView_drawableLeft
147 * @attr ref android.R.styleable#TextView_lineSpacingExtra
148 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
149 */
150@RemoteView
151public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
152    private static int PRIORITY = 100;
153
154    private ColorStateList mTextColor;
155    private int mCurTextColor;
156    private ColorStateList mHintTextColor;
157    private ColorStateList mLinkTextColor;
158    private int mCurHintTextColor;
159    private boolean mFreezesText;
160    private boolean mFrozenWithFocus;
161
162    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
163    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
164
165    private float mShadowRadius, mShadowDx, mShadowDy;
166
167    private static final int PREDRAW_NOT_REGISTERED = 0;
168    private static final int PREDRAW_PENDING = 1;
169    private static final int PREDRAW_DONE = 2;
170    private int mPreDrawState = PREDRAW_NOT_REGISTERED;
171
172    private TextUtils.TruncateAt mEllipsize = null;
173
174    // Enum for the "typeface" XML parameter.
175    // TODO: How can we get this from the XML instead of hardcoding it here?
176    private static final int SANS = 1;
177    private static final int SERIF = 2;
178    private static final int MONOSPACE = 3;
179
180    // Bitfield for the "numeric" XML parameter.
181    // TODO: How can we get this from the XML instead of hardcoding it here?
182    private static final int SIGNED = 2;
183    private static final int DECIMAL = 4;
184
185    private Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
186    private int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
187    private int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
188    private boolean mDrawables;
189    private int mDrawablePadding;
190
191    private CharSequence mError;
192    private boolean mErrorWasChanged;
193    private PopupWindow mPopup;
194
195    private CharWrapper mCharWrapper = null;
196    private Rect mCompoundRect;
197
198    private boolean mSelectionMoved = false;
199
200    /*
201     * Kick-start the font cache for the zygote process (to pay the cost of
202     * initializing freetype for our default font only once).
203     */
204    static {
205        Paint p = new Paint();
206        p.setAntiAlias(true);
207        // We don't care about the result, just the side-effect of measuring.
208        p.measureText("H");
209    }
210
211    public TextView(Context context) {
212        this(context, null);
213    }
214
215    public TextView(Context context,
216                    AttributeSet attrs) {
217        this(context, attrs, com.android.internal.R.attr.textViewStyle);
218    }
219
220    public TextView(Context context,
221                    AttributeSet attrs,
222                    int defStyle) {
223        super(context, attrs, defStyle);
224
225        mText = "";
226
227        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
228        // If we get the paint from the skin, we should set it to left, since
229        // the layout always wants it to be left.
230        // mTextPaint.setTextAlign(Paint.Align.LEFT);
231
232        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
233
234        mMovement = getDefaultMovementMethod();
235        mTransformation = null;
236
237        TypedArray a =
238            context.obtainStyledAttributes(
239                attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
240
241        int textColorHighlight = 0;
242        ColorStateList textColor = null;
243        ColorStateList textColorHint = null;
244        ColorStateList textColorLink = null;
245        int textSize = 15;
246        int typefaceIndex = -1;
247        int styleIndex = -1;
248
249        /*
250         * Look the appearance up without checking first if it exists because
251         * almost every TextView has one and it greatly simplifies the logic
252         * to be able to parse the appearance first and then let specific tags
253         * for this View override it.
254         */
255        TypedArray appearance = null;
256        int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
257        if (ap != -1) {
258            appearance = context.obtainStyledAttributes(ap,
259                                com.android.internal.R.styleable.
260                                TextAppearance);
261        }
262        if (appearance != null) {
263            int n = appearance.getIndexCount();
264            for (int i = 0; i < n; i++) {
265                int attr = appearance.getIndex(i);
266
267                switch (attr) {
268                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
269                    textColorHighlight = appearance.getColor(attr, textColorHighlight);
270                    break;
271
272                case com.android.internal.R.styleable.TextAppearance_textColor:
273                    textColor = appearance.getColorStateList(attr);
274                    break;
275
276                case com.android.internal.R.styleable.TextAppearance_textColorHint:
277                    textColorHint = appearance.getColorStateList(attr);
278                    break;
279
280                case com.android.internal.R.styleable.TextAppearance_textColorLink:
281                    textColorLink = appearance.getColorStateList(attr);
282                    break;
283
284                case com.android.internal.R.styleable.TextAppearance_textSize:
285                    textSize = appearance.getDimensionPixelSize(attr, textSize);
286                    break;
287
288                case com.android.internal.R.styleable.TextAppearance_typeface:
289                    typefaceIndex = appearance.getInt(attr, -1);
290                    break;
291
292                case com.android.internal.R.styleable.TextAppearance_textStyle:
293                    styleIndex = appearance.getInt(attr, -1);
294                    break;
295                }
296            }
297
298            appearance.recycle();
299        }
300
301        boolean editable = getDefaultEditable();
302        CharSequence inputMethod = null;
303        int numeric = 0;
304        CharSequence digits = null;
305        boolean phone = false;
306        boolean autotext = false;
307        int autocap = -1;
308        int buffertype = 0;
309        boolean selectallonfocus = false;
310        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
311            drawableBottom = null;
312        int drawablePadding = 0;
313        int ellipsize = -1;
314        boolean singleLine = false;
315        int maxlength = -1;
316        CharSequence text = "";
317        int shadowcolor = 0;
318        float dx = 0, dy = 0, r = 0;
319        boolean password = false;
320
321        int n = a.getIndexCount();
322        for (int i = 0; i < n; i++) {
323            int attr = a.getIndex(i);
324
325            switch (attr) {
326            case com.android.internal.R.styleable.TextView_editable:
327                editable = a.getBoolean(attr, editable);
328                break;
329
330            case com.android.internal.R.styleable.TextView_inputMethod:
331                inputMethod = a.getText(attr);
332                break;
333
334            case com.android.internal.R.styleable.TextView_numeric:
335                numeric = a.getInt(attr, numeric);
336                break;
337
338            case com.android.internal.R.styleable.TextView_digits:
339                digits = a.getText(attr);
340                break;
341
342            case com.android.internal.R.styleable.TextView_phoneNumber:
343                phone = a.getBoolean(attr, phone);
344                break;
345
346            case com.android.internal.R.styleable.TextView_autoText:
347                autotext = a.getBoolean(attr, autotext);
348                break;
349
350            case com.android.internal.R.styleable.TextView_capitalize:
351                autocap = a.getInt(attr, autocap);
352                break;
353
354            case com.android.internal.R.styleable.TextView_bufferType:
355                buffertype = a.getInt(attr, buffertype);
356                break;
357
358            case com.android.internal.R.styleable.TextView_selectAllOnFocus:
359                selectallonfocus = a.getBoolean(attr, selectallonfocus);
360                break;
361
362            case com.android.internal.R.styleable.TextView_autoLink:
363                mAutoLinkMask = a.getInt(attr, 0);
364                break;
365
366            case com.android.internal.R.styleable.TextView_linksClickable:
367                mLinksClickable = a.getBoolean(attr, true);
368                break;
369
370            case com.android.internal.R.styleable.TextView_drawableLeft:
371                drawableLeft = a.getDrawable(attr);
372                break;
373
374            case com.android.internal.R.styleable.TextView_drawableTop:
375                drawableTop = a.getDrawable(attr);
376                break;
377
378            case com.android.internal.R.styleable.TextView_drawableRight:
379                drawableRight = a.getDrawable(attr);
380                break;
381
382            case com.android.internal.R.styleable.TextView_drawableBottom:
383                drawableBottom = a.getDrawable(attr);
384                break;
385
386            case com.android.internal.R.styleable.TextView_drawablePadding:
387                drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
388                break;
389
390            case com.android.internal.R.styleable.TextView_maxLines:
391                setMaxLines(a.getInt(attr, -1));
392                break;
393
394            case com.android.internal.R.styleable.TextView_maxHeight:
395                setMaxHeight(a.getDimensionPixelSize(attr, -1));
396                break;
397
398            case com.android.internal.R.styleable.TextView_lines:
399                setLines(a.getInt(attr, -1));
400                break;
401
402            case com.android.internal.R.styleable.TextView_height:
403                setHeight(a.getDimensionPixelSize(attr, -1));
404                break;
405
406            case com.android.internal.R.styleable.TextView_minLines:
407                setMinLines(a.getInt(attr, -1));
408                break;
409
410            case com.android.internal.R.styleable.TextView_minHeight:
411                setMinHeight(a.getDimensionPixelSize(attr, -1));
412                break;
413
414            case com.android.internal.R.styleable.TextView_maxEms:
415                setMaxEms(a.getInt(attr, -1));
416                break;
417
418            case com.android.internal.R.styleable.TextView_maxWidth:
419                setMaxWidth(a.getDimensionPixelSize(attr, -1));
420                break;
421
422            case com.android.internal.R.styleable.TextView_ems:
423                setEms(a.getInt(attr, -1));
424                break;
425
426            case com.android.internal.R.styleable.TextView_width:
427                setWidth(a.getDimensionPixelSize(attr, -1));
428                break;
429
430            case com.android.internal.R.styleable.TextView_minEms:
431                setMinEms(a.getInt(attr, -1));
432                break;
433
434            case com.android.internal.R.styleable.TextView_minWidth:
435                setMinWidth(a.getDimensionPixelSize(attr, -1));
436                break;
437
438            case com.android.internal.R.styleable.TextView_gravity:
439                setGravity(a.getInt(attr, -1));
440                break;
441
442            case com.android.internal.R.styleable.TextView_hint:
443                setHint(a.getText(attr));
444                break;
445
446            case com.android.internal.R.styleable.TextView_text:
447                text = a.getText(attr);
448                break;
449
450            case com.android.internal.R.styleable.TextView_scrollHorizontally:
451                if (a.getBoolean(attr, false)) {
452                    setHorizontallyScrolling(true);
453                }
454                break;
455
456            case com.android.internal.R.styleable.TextView_singleLine:
457                singleLine = a.getBoolean(attr, singleLine);
458                break;
459
460            case com.android.internal.R.styleable.TextView_ellipsize:
461                ellipsize = a.getInt(attr, ellipsize);
462                break;
463
464            case com.android.internal.R.styleable.TextView_includeFontPadding:
465                if (!a.getBoolean(attr, true)) {
466                    setIncludeFontPadding(false);
467                }
468                break;
469
470            case com.android.internal.R.styleable.TextView_cursorVisible:
471                if (!a.getBoolean(attr, true)) {
472                    setCursorVisible(false);
473                }
474                break;
475
476            case com.android.internal.R.styleable.TextView_maxLength:
477                maxlength = a.getInt(attr, -1);
478                break;
479
480            case com.android.internal.R.styleable.TextView_textScaleX:
481                setTextScaleX(a.getFloat(attr, 1.0f));
482                break;
483
484            case com.android.internal.R.styleable.TextView_freezesText:
485                mFreezesText = a.getBoolean(attr, false);
486                break;
487
488            case com.android.internal.R.styleable.TextView_shadowColor:
489                shadowcolor = a.getInt(attr, 0);
490                break;
491
492            case com.android.internal.R.styleable.TextView_shadowDx:
493                dx = a.getFloat(attr, 0);
494                break;
495
496            case com.android.internal.R.styleable.TextView_shadowDy:
497                dy = a.getFloat(attr, 0);
498                break;
499
500            case com.android.internal.R.styleable.TextView_shadowRadius:
501                r = a.getFloat(attr, 0);
502                break;
503
504            case com.android.internal.R.styleable.TextView_enabled:
505                setEnabled(a.getBoolean(attr, isEnabled()));
506                break;
507
508            case com.android.internal.R.styleable.TextView_textColorHighlight:
509                textColorHighlight = a.getColor(attr, textColorHighlight);
510                break;
511
512            case com.android.internal.R.styleable.TextView_textColor:
513                textColor = a.getColorStateList(attr);
514                break;
515
516            case com.android.internal.R.styleable.TextView_textColorHint:
517                textColorHint = a.getColorStateList(attr);
518                break;
519
520            case com.android.internal.R.styleable.TextView_textColorLink:
521                textColorLink = a.getColorStateList(attr);
522                break;
523
524            case com.android.internal.R.styleable.TextView_textSize:
525                textSize = a.getDimensionPixelSize(attr, textSize);
526                break;
527
528            case com.android.internal.R.styleable.TextView_typeface:
529                typefaceIndex = a.getInt(attr, typefaceIndex);
530                break;
531
532            case com.android.internal.R.styleable.TextView_textStyle:
533                styleIndex = a.getInt(attr, styleIndex);
534                break;
535
536            case com.android.internal.R.styleable.TextView_password:
537                password = a.getBoolean(attr, password);
538                break;
539
540            case com.android.internal.R.styleable.TextView_lineSpacingExtra:
541                mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
542                break;
543
544            case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
545                mSpacingMult = a.getFloat(attr, mSpacingMult);
546                break;
547            }
548        }
549        a.recycle();
550
551        BufferType bufferType = BufferType.EDITABLE;
552
553        if (inputMethod != null) {
554            Class c;
555
556            try {
557                c = Class.forName(inputMethod.toString());
558            } catch (ClassNotFoundException ex) {
559                throw new RuntimeException(ex);
560            }
561
562            try {
563                mInput = (KeyListener) c.newInstance();
564            } catch (InstantiationException ex) {
565                throw new RuntimeException(ex);
566            } catch (IllegalAccessException ex) {
567                throw new RuntimeException(ex);
568            }
569        } else if (digits != null) {
570            mInput = DigitsKeyListener.getInstance(digits.toString());
571        } else if (phone) {
572            mInput = DialerKeyListener.getInstance();
573        } else if (numeric != 0) {
574            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
575                                                   (numeric & DECIMAL) != 0);
576        } else if (autotext || autocap != -1) {
577            TextKeyListener.Capitalize cap;
578
579            switch (autocap) {
580            case 1:
581                cap = TextKeyListener.Capitalize.SENTENCES;
582                break;
583
584            case 2:
585                cap = TextKeyListener.Capitalize.WORDS;
586                break;
587
588            case 3:
589                cap = TextKeyListener.Capitalize.CHARACTERS;
590                break;
591
592            default:
593                cap = TextKeyListener.Capitalize.NONE;
594                break;
595            }
596
597            mInput = TextKeyListener.getInstance(autotext, cap);
598        } else if (editable) {
599            mInput = TextKeyListener.getInstance();
600        } else {
601            mInput = null;
602
603            switch (buffertype) {
604                case 0:
605                    bufferType = BufferType.NORMAL;
606                    break;
607                case 1:
608                    bufferType = BufferType.SPANNABLE;
609                    break;
610                case 2:
611                    bufferType = BufferType.EDITABLE;
612                    break;
613            }
614        }
615
616        if (selectallonfocus) {
617            mSelectAllOnFocus = true;
618
619            if (bufferType == BufferType.NORMAL)
620                bufferType = BufferType.SPANNABLE;
621        }
622
623        setCompoundDrawablesWithIntrinsicBounds(
624            drawableLeft, drawableTop, drawableRight, drawableBottom);
625        setCompoundDrawablePadding(drawablePadding);
626
627        if (singleLine) {
628            setSingleLine();
629
630            if (mInput == null && ellipsize < 0) {
631                ellipsize = 3; // END
632            }
633        }
634
635        switch (ellipsize) {
636            case 1:
637                setEllipsize(TextUtils.TruncateAt.START);
638                break;
639            case 2:
640                setEllipsize(TextUtils.TruncateAt.MIDDLE);
641                break;
642            case 3:
643                setEllipsize(TextUtils.TruncateAt.END);
644                break;
645        }
646
647        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
648        setHintTextColor(textColorHint);
649        setLinkTextColor(textColorLink);
650        if (textColorHighlight != 0) {
651            setHighlightColor(textColorHighlight);
652        }
653        setRawTextSize(textSize);
654
655        if (password) {
656            setTransformationMethod(PasswordTransformationMethod.getInstance());
657            typefaceIndex = MONOSPACE;
658        }
659
660        setTypefaceByIndex(typefaceIndex, styleIndex);
661
662        if (shadowcolor != 0) {
663            setShadowLayer(r, dx, dy, shadowcolor);
664        }
665
666        if (maxlength >= 0) {
667            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
668        } else {
669            setFilters(NO_FILTERS);
670        }
671
672        setText(text, bufferType);
673
674        /*
675         * Views are not normally focusable unless specified to be.
676         * However, TextViews that have input or movement methods *are*
677         * focusable by default.
678         */
679        a = context.obtainStyledAttributes(attrs,
680                                           com.android.internal.R.styleable.View,
681                                           defStyle, 0);
682
683        boolean focusable = mMovement != null || mInput != null;
684        boolean clickable = focusable;
685        boolean longClickable = focusable;
686
687        n = a.getIndexCount();
688        for (int i = 0; i < n; i++) {
689            int attr = a.getIndex(i);
690
691            switch (attr) {
692            case com.android.internal.R.styleable.View_focusable:
693                focusable = a.getBoolean(attr, focusable);
694                break;
695
696            case com.android.internal.R.styleable.View_clickable:
697                clickable = a.getBoolean(attr, clickable);
698                break;
699
700            case com.android.internal.R.styleable.View_longClickable:
701                longClickable = a.getBoolean(attr, longClickable);
702                break;
703            }
704        }
705        a.recycle();
706
707        setFocusable(focusable);
708        setClickable(clickable);
709        setLongClickable(longClickable);
710    }
711
712    private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
713        Typeface tf = null;
714        switch (typefaceIndex) {
715            case SANS:
716                tf = Typeface.SANS_SERIF;
717                break;
718
719            case SERIF:
720                tf = Typeface.SERIF;
721                break;
722
723            case MONOSPACE:
724                tf = Typeface.MONOSPACE;
725                break;
726        }
727
728        setTypeface(tf, styleIndex);
729    }
730
731    /**
732     * Sets the typeface and style in which the text should be displayed,
733     * and turns on the fake bold and italic bits in the Paint if the
734     * Typeface that you provided does not have all the bits in the
735     * style that you specified.
736     *
737     * @attr ref android.R.styleable#TextView_typeface
738     * @attr ref android.R.styleable#TextView_textStyle
739     */
740    public void setTypeface(Typeface tf, int style) {
741        if (style > 0) {
742            if (tf == null) {
743                tf = Typeface.defaultFromStyle(style);
744            } else {
745                tf = Typeface.create(tf, style);
746            }
747
748            setTypeface(tf);
749            // now compute what (if any) algorithmic styling is needed
750            int typefaceStyle = tf != null ? tf.getStyle() : 0;
751            int need = style & ~typefaceStyle;
752            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
753            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
754        } else {
755            mTextPaint.setFakeBoldText(false);
756            mTextPaint.setTextSkewX(0);
757            setTypeface(tf);
758        }
759    }
760
761    /**
762     * Subclasses override this to specify that they have a KeyListener
763     * by default even if not specifically called for in the XML options.
764     */
765    protected boolean getDefaultEditable() {
766        return false;
767    }
768
769    /**
770     * Subclasses override this to specify a default movement method.
771     */
772    protected MovementMethod getDefaultMovementMethod() {
773        return null;
774    }
775
776    /**
777     * Return the text the TextView is displaying.  If setText() was called
778     * with an argument of BufferType.SPANNABLE or BufferType.EDITABLE,
779     * you can cast the return value from this method to Spannable
780     * or Editable, respectively.
781     */
782    public CharSequence getText() {
783        return mText;
784    }
785
786    /**
787     * Returns the length, in characters, of the text managed by this TextView
788     */
789    public int length() {
790        return mText.length();
791    }
792
793    /**
794     * @return the height of one standard line in pixels.  Note that markup
795     * within the text can cause individual lines to be taller or shorter
796     * than this height, and the layout may contain additional first-
797     * or last-line padding.
798     */
799    public int getLineHeight() {
800        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
801                          + mSpacingAdd);
802    }
803
804    /**
805     * @return the Layout that is currently being used to display the text.
806     * This can be null if the text or width has recently changes.
807     */
808    public final Layout getLayout() {
809        return mLayout;
810    }
811
812    /**
813     * @return the current key listener for this TextView.
814     * This will frequently be null for non-EditText TextViews.
815     */
816    public final KeyListener getKeyListener() {
817        return mInput;
818    }
819
820    /**
821     * Sets the key listener to be used with this TextView.  This can be null
822     * to disallow user input.
823     * <p>
824     * Be warned that if you want a TextView with a key listener or movement
825     * method not to be focusable, or if you want a TextView without a
826     * key listener or movement method to be focusable, you must call
827     * {@link #setFocusable} again after calling this to get the focusability
828     * back the way you want it.
829     *
830     * @attr ref android.R.styleable#TextView_numeric
831     * @attr ref android.R.styleable#TextView_digits
832     * @attr ref android.R.styleable#TextView_phoneNumber
833     * @attr ref android.R.styleable#TextView_inputMethod
834     * @attr ref android.R.styleable#TextView_capitalize
835     * @attr ref android.R.styleable#TextView_autoText
836     */
837    public void setKeyListener(KeyListener input) {
838        mInput = input;
839
840        if (mInput != null && !(mText instanceof Editable))
841            setText(mText);
842
843        setFilters((Editable) mText, mFilters);
844        fixFocusableAndClickableSettings();
845    }
846
847    /**
848     * @return the movement method being used for this TextView.
849     * This will frequently be null for non-EditText TextViews.
850     */
851    public final MovementMethod getMovementMethod() {
852        return mMovement;
853    }
854
855    /**
856     * Sets the movement method (arrow key handler) to be used for
857     * this TextView.  This can be null to disallow using the arrow keys
858     * to move the cursor or scroll the view.
859     * <p>
860     * Be warned that if you want a TextView with a key listener or movement
861     * method not to be focusable, or if you want a TextView without a
862     * key listener or movement method to be focusable, you must call
863     * {@link #setFocusable} again after calling this to get the focusability
864     * back the way you want it.
865     */
866    public final void setMovementMethod(MovementMethod movement) {
867        mMovement = movement;
868
869        if (mMovement != null && !(mText instanceof Spannable))
870            setText(mText);
871
872        fixFocusableAndClickableSettings();
873    }
874
875    private void fixFocusableAndClickableSettings() {
876        if (mMovement != null || mInput != null) {
877            setFocusable(true);
878            setClickable(true);
879            setLongClickable(true);
880        } else {
881            setFocusable(false);
882            setClickable(false);
883            setLongClickable(false);
884        }
885    }
886
887    /**
888     * @return the current transformation method for this TextView.
889     * This will frequently be null except for single-line and password
890     * fields.
891     */
892    public final TransformationMethod getTransformationMethod() {
893        return mTransformation;
894    }
895
896    /**
897     * Sets the transformation that is applied to the text that this
898     * TextView is displaying.
899     *
900     * @attr ref android.R.styleable#TextView_password
901     * @attr ref android.R.styleable#TextView_singleLine
902     */
903    public final void setTransformationMethod(TransformationMethod method) {
904        if (mTransformation != null) {
905            if (mText instanceof Spannable) {
906                ((Spannable) mText).removeSpan(mTransformation);
907            }
908        }
909
910        mTransformation = method;
911
912        setText(mText);
913    }
914
915    /**
916     * Returns the top padding of the view, plus space for the top
917     * Drawable if any.
918     */
919    public int getCompoundPaddingTop() {
920        if (mDrawableTop == null) {
921            return mPaddingTop;
922        } else {
923            return mPaddingTop + mDrawablePadding + mDrawableSizeTop;
924        }
925    }
926
927    /**
928     * Returns the bottom padding of the view, plus space for the bottom
929     * Drawable if any.
930     */
931    public int getCompoundPaddingBottom() {
932        if (mDrawableBottom == null) {
933            return mPaddingBottom;
934        } else {
935            return mPaddingBottom + mDrawablePadding + mDrawableSizeBottom;
936        }
937    }
938
939    /**
940     * Returns the left padding of the view, plus space for the left
941     * Drawable if any.
942     */
943    public int getCompoundPaddingLeft() {
944        if (mDrawableLeft == null) {
945            return mPaddingLeft;
946        } else {
947            return mPaddingLeft + mDrawablePadding + mDrawableSizeLeft;
948        }
949    }
950
951    /**
952     * Returns the right padding of the view, plus space for the right
953     * Drawable if any.
954     */
955    public int getCompoundPaddingRight() {
956        if (mDrawableRight == null) {
957            return mPaddingRight;
958        } else {
959            return mPaddingRight + mDrawablePadding + mDrawableSizeRight;
960        }
961    }
962
963    /**
964     * Returns the extended top padding of the view, including both the
965     * top Drawable if any and any extra space to keep more than maxLines
966     * of text from showing.  It is only valid to call this after measuring.
967     */
968    public int getExtendedPaddingTop() {
969        if (mMaxMode != LINES) {
970            return getCompoundPaddingTop();
971        }
972
973        if (mLayout.getLineCount() <= mMaximum) {
974            return getCompoundPaddingTop();
975        }
976
977        int top = getCompoundPaddingTop();
978        int bottom = getCompoundPaddingBottom();
979        int viewht = getHeight() - top - bottom;
980        int layoutht = mLayout.getLineTop(mMaximum);
981
982        if (layoutht >= viewht) {
983            return top;
984        }
985
986        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
987        if (gravity == Gravity.TOP) {
988            return top;
989        } else if (gravity == Gravity.BOTTOM) {
990            return top + viewht - layoutht;
991        } else { // (gravity == Gravity.CENTER_VERTICAL)
992            return top + (viewht - layoutht) / 2;
993        }
994    }
995
996    /**
997     * Returns the extended bottom padding of the view, including both the
998     * bottom Drawable if any and any extra space to keep more than maxLines
999     * of text from showing.  It is only valid to call this after measuring.
1000     */
1001    public int getExtendedPaddingBottom() {
1002        if (mMaxMode != LINES) {
1003            return getCompoundPaddingBottom();
1004        }
1005
1006        if (mLayout.getLineCount() <= mMaximum) {
1007            return getCompoundPaddingBottom();
1008        }
1009
1010        int top = getCompoundPaddingTop();
1011        int bottom = getCompoundPaddingBottom();
1012        int viewht = getHeight() - top - bottom;
1013        int layoutht = mLayout.getLineTop(mMaximum);
1014
1015        if (layoutht >= viewht) {
1016            return bottom;
1017        }
1018
1019        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1020        if (gravity == Gravity.TOP) {
1021            return bottom + viewht - layoutht;
1022        } else if (gravity == Gravity.BOTTOM) {
1023            return bottom;
1024        } else { // (gravity == Gravity.CENTER_VERTICAL)
1025            return bottom + (viewht - layoutht) / 2;
1026        }
1027    }
1028
1029    /**
1030     * Returns the total left padding of the view, including the left
1031     * Drawable if any.
1032     */
1033    public int getTotalPaddingLeft() {
1034        return getCompoundPaddingLeft();
1035    }
1036
1037    /**
1038     * Returns the total right padding of the view, including the right
1039     * Drawable if any.
1040     */
1041    public int getTotalPaddingRight() {
1042        return getCompoundPaddingRight();
1043    }
1044
1045    /**
1046     * Returns the total top padding of the view, including the top
1047     * Drawable if any, the extra space to keep more than maxLines
1048     * from showing, and the vertical offset for gravity, if any.
1049     */
1050    public int getTotalPaddingTop() {
1051        return getExtendedPaddingTop() + getVerticalOffset(true);
1052    }
1053
1054    /**
1055     * Returns the total bottom padding of the view, including the bottom
1056     * Drawable if any, the extra space to keep more than maxLines
1057     * from showing, and the vertical offset for gravity, if any.
1058     */
1059    public int getTotalPaddingBottom() {
1060        return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1061    }
1062
1063    /**
1064     * Sets the Drawables (if any) to appear to the left of, above,
1065     * to the right of, and below the text.  Use null if you do not
1066     * want a Drawable there.  The Drawables must already have had
1067     * {@link Drawable#setBounds} called.
1068     *
1069     * @attr ref android.R.styleable#TextView_drawableLeft
1070     * @attr ref android.R.styleable#TextView_drawableTop
1071     * @attr ref android.R.styleable#TextView_drawableRight
1072     * @attr ref android.R.styleable#TextView_drawableBottom
1073     */
1074    public void setCompoundDrawables(Drawable left, Drawable top,
1075                                     Drawable right, Drawable bottom) {
1076        mDrawableLeft = left;
1077        mDrawableTop = top;
1078        mDrawableRight = right;
1079        mDrawableBottom = bottom;
1080
1081        mDrawables = mDrawableLeft != null
1082                || mDrawableRight != null
1083                || mDrawableTop != null
1084                || mDrawableBottom != null;
1085
1086        if (mCompoundRect == null &&
1087                (left != null || top != null || right != null || bottom != null)) {
1088            mCompoundRect = new Rect();
1089        }
1090
1091        final Rect compoundRect = mCompoundRect;
1092        int[] state = null;
1093
1094        if (mDrawables) {
1095            state = getDrawableState();
1096        }
1097
1098        if (mDrawableLeft != null) {
1099            mDrawableLeft.setState(state);
1100            mDrawableLeft.copyBounds(compoundRect);
1101            mDrawableSizeLeft = compoundRect.width();
1102            mDrawableHeightLeft = compoundRect.height();
1103        } else {
1104            mDrawableSizeLeft = mDrawableHeightLeft = 0;
1105        }
1106
1107        if (mDrawableRight != null) {
1108            mDrawableRight.setState(state);
1109            mDrawableRight.copyBounds(compoundRect);
1110            mDrawableSizeRight = compoundRect.width();
1111            mDrawableHeightRight = compoundRect.height();
1112        } else {
1113            mDrawableSizeRight = mDrawableHeightRight = 0;
1114        }
1115
1116        if (mDrawableTop != null) {
1117            mDrawableTop.setState(state);
1118            mDrawableTop.copyBounds(compoundRect);
1119            mDrawableSizeTop = compoundRect.height();
1120            mDrawableWidthTop = compoundRect.width();
1121        } else {
1122            mDrawableSizeTop = mDrawableWidthTop = 0;
1123        }
1124
1125        if (mDrawableBottom != null) {
1126            mDrawableBottom.setState(state);
1127            mDrawableBottom.copyBounds(compoundRect);
1128            mDrawableSizeBottom = compoundRect.height();
1129            mDrawableWidthBottom = compoundRect.width();
1130        } else {
1131            mDrawableSizeBottom = mDrawableWidthBottom = 0;
1132        }
1133
1134        invalidate();
1135        requestLayout();
1136    }
1137
1138    /**
1139     * Sets the Drawables (if any) to appear to the left of, above,
1140     * to the right of, and below the text.  Use null if you do not
1141     * want a Drawable there.  The Drawables' bounds will be set to
1142     * their intrinsic bounds.
1143     *
1144     * @attr ref android.R.styleable#TextView_drawableLeft
1145     * @attr ref android.R.styleable#TextView_drawableTop
1146     * @attr ref android.R.styleable#TextView_drawableRight
1147     * @attr ref android.R.styleable#TextView_drawableBottom
1148     */
1149    public void setCompoundDrawablesWithIntrinsicBounds(Drawable left,
1150                                     Drawable top,
1151                                     Drawable right, Drawable bottom) {
1152        if (left != null) {
1153            left.setBounds(0, 0,
1154                           left.getIntrinsicWidth(), left.getIntrinsicHeight());
1155        }
1156        if (right != null) {
1157            right.setBounds(0, 0,
1158                           right.getIntrinsicWidth(), right.getIntrinsicHeight());
1159        }
1160        if (top != null) {
1161            top.setBounds(0, 0,
1162                           top.getIntrinsicWidth(), top.getIntrinsicHeight());
1163        }
1164        if (bottom != null) {
1165            bottom.setBounds(0, 0,
1166                           bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1167        }
1168        setCompoundDrawables(left, top, right, bottom);
1169    }
1170
1171    /**
1172     * Returns drawables for the left, top, right, and bottom borders.
1173     */
1174    public Drawable[] getCompoundDrawables() {
1175        return new Drawable[] {
1176            mDrawableLeft, mDrawableTop, mDrawableRight, mDrawableBottom
1177        };
1178    }
1179
1180    /**
1181     * Sets the size of the padding between the compound drawables and
1182     * the text.
1183     *
1184     * @attr ref android.R.styleable#TextView_drawablePadding
1185     */
1186    public void setCompoundDrawablePadding(int pad) {
1187        mDrawablePadding = pad;
1188
1189        invalidate();
1190        requestLayout();
1191    }
1192
1193    /**
1194     * Returns the padding between the compound drawables and the text.
1195     */
1196    public int getCompoundDrawablePadding() {
1197        return mDrawablePadding;
1198    }
1199
1200    @Override
1201    public void setPadding(int left, int top, int right, int bottom) {
1202        if (left != getPaddingLeft() ||
1203            right != getPaddingRight() ||
1204            top != getPaddingTop() ||
1205            bottom != getPaddingBottom()) {
1206            nullLayouts();
1207        }
1208
1209        // the super call will requestLayout()
1210        super.setPadding(left, top, right, bottom);
1211        invalidate();
1212    }
1213
1214    /**
1215     * Gets the autolink mask of the text.  See {@link
1216     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1217     * possible values.
1218     *
1219     * @attr ref android.R.styleable#TextView_autoLink
1220     */
1221    public final int getAutoLinkMask() {
1222        return mAutoLinkMask;
1223    }
1224
1225    /**
1226     * Sets the text color, size, style, hint color, and highlight color
1227     * from the specified TextAppearance resource.
1228     */
1229    public void setTextAppearance(Context context, int resid) {
1230        TypedArray appearance =
1231            context.obtainStyledAttributes(resid,
1232                                           com.android.internal.R.styleable.TextAppearance);
1233
1234        int color;
1235        ColorStateList colors;
1236        int ts;
1237
1238        color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
1239        if (color != 0) {
1240            setHighlightColor(color);
1241        }
1242
1243        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1244                                              TextAppearance_textColor);
1245        if (colors != null) {
1246            setTextColor(colors);
1247        }
1248
1249        ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
1250                                              TextAppearance_textSize, 0);
1251        if (ts != 0) {
1252            setRawTextSize(ts);
1253        }
1254
1255        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1256                                              TextAppearance_textColorHint);
1257        if (colors != null) {
1258            setHintTextColor(colors);
1259        }
1260
1261        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1262                                              TextAppearance_textColorLink);
1263        if (colors != null) {
1264            setLinkTextColor(colors);
1265        }
1266
1267        int typefaceIndex, styleIndex;
1268
1269        typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
1270                                          TextAppearance_typeface, -1);
1271        styleIndex = appearance.getInt(com.android.internal.R.styleable.
1272                                       TextAppearance_textStyle, -1);
1273
1274        setTypefaceByIndex(typefaceIndex, styleIndex);
1275        appearance.recycle();
1276    }
1277
1278    /**
1279     * @return the size (in pixels) of the default text size in this TextView.
1280     */
1281    public float getTextSize() {
1282        return mTextPaint.getTextSize();
1283    }
1284
1285    /**
1286     * Set the default text size to the given value, interpreted as "scaled
1287     * pixel" units.  This size is adjusted based on the current density and
1288     * user font size preference.
1289     *
1290     * @param size The scaled pixel size.
1291     *
1292     * @attr ref android.R.styleable#TextView_textSize
1293     */
1294    public void setTextSize(float size) {
1295        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
1296    }
1297
1298    /**
1299     * Set the default text size to a given unit and value.  See {@link
1300     * TypedValue} for the possible dimension units.
1301     *
1302     * @param unit The desired dimension unit.
1303     * @param size The desired size in the given units.
1304     *
1305     * @attr ref android.R.styleable#TextView_textSize
1306     */
1307    public void setTextSize(int unit, float size) {
1308        Context c = getContext();
1309        Resources r;
1310
1311        if (c == null)
1312            r = Resources.getSystem();
1313        else
1314            r = c.getResources();
1315
1316        setRawTextSize(TypedValue.applyDimension(
1317            unit, size, r.getDisplayMetrics()));
1318    }
1319
1320    private void setRawTextSize(float size) {
1321        if (size != mTextPaint.getTextSize()) {
1322            mTextPaint.setTextSize(size);
1323
1324            if (mLayout != null) {
1325                nullLayouts();
1326                requestLayout();
1327                invalidate();
1328            }
1329        }
1330    }
1331
1332    /**
1333     * @return the extent by which text is currently being stretched
1334     * horizontally.  This will usually be 1.
1335     */
1336    public float getTextScaleX() {
1337        return mTextPaint.getTextScaleX();
1338    }
1339
1340    /**
1341     * Sets the extent by which text should be stretched horizontally.
1342     *
1343     * @attr ref android.R.styleable#TextView_textScaleX
1344     */
1345    public void setTextScaleX(float size) {
1346        if (size != mTextPaint.getTextScaleX()) {
1347            mTextPaint.setTextScaleX(size);
1348
1349            if (mLayout != null) {
1350                nullLayouts();
1351                requestLayout();
1352                invalidate();
1353            }
1354        }
1355    }
1356
1357    /**
1358     * Sets the typeface and style in which the text should be displayed.
1359     * Note that not all Typeface families actually have bold and italic
1360     * variants, so you may need to use
1361     * {@link #setTypeface(Typeface, int)} to get the appearance
1362     * that you actually want.
1363     *
1364     * @attr ref android.R.styleable#TextView_typeface
1365     * @attr ref android.R.styleable#TextView_textStyle
1366     */
1367    public void setTypeface(Typeface tf) {
1368        if (mTextPaint.getTypeface() != tf) {
1369            mTextPaint.setTypeface(tf);
1370
1371            if (mLayout != null) {
1372                nullLayouts();
1373                requestLayout();
1374                invalidate();
1375            }
1376        }
1377    }
1378
1379    /**
1380     * @return the current typeface and style in which the text is being
1381     * displayed.
1382     */
1383    public Typeface getTypeface() {
1384        return mTextPaint.getTypeface();
1385    }
1386
1387    /**
1388     * Sets the text color for all the states (normal, selected,
1389     * focused) to be this color.
1390     *
1391     * @attr ref android.R.styleable#TextView_textColor
1392     */
1393    public void setTextColor(int color) {
1394        mTextColor = ColorStateList.valueOf(color);
1395        updateTextColors();
1396    }
1397
1398    /**
1399     * Sets the text color.
1400     *
1401     * @attr ref android.R.styleable#TextView_textColor
1402     */
1403    public void setTextColor(ColorStateList colors) {
1404        if (colors == null) {
1405            throw new NullPointerException();
1406        }
1407
1408        mTextColor = colors;
1409        updateTextColors();
1410    }
1411
1412    /**
1413     * Return the set of text colors.
1414     *
1415     * @return Returns the set of text colors.
1416     */
1417    public final ColorStateList getTextColors() {
1418        return mTextColor;
1419    }
1420
1421    /**
1422     * <p>Return the current color selected for normal text.</p>
1423     *
1424     * @return Returns the current text color.
1425     */
1426    public final int getCurrentTextColor() {
1427        return mCurTextColor;
1428    }
1429
1430    /**
1431     * Sets the color used to display the selection highlight.
1432     *
1433     * @attr ref android.R.styleable#TextView_textColorHighlight
1434     */
1435    public void setHighlightColor(int color) {
1436        if (mHighlightColor != color) {
1437            mHighlightColor = color;
1438            invalidate();
1439        }
1440    }
1441
1442    /**
1443     * Gives the text a shadow of the specified radius and color, the specified
1444     * distance from its normal position.
1445     *
1446     * @attr ref android.R.styleable#TextView_shadowColor
1447     * @attr ref android.R.styleable#TextView_shadowDx
1448     * @attr ref android.R.styleable#TextView_shadowDy
1449     * @attr ref android.R.styleable#TextView_shadowRadius
1450     */
1451    public void setShadowLayer(float radius, float dx, float dy, int color) {
1452        mTextPaint.setShadowLayer(radius, dx, dy, color);
1453
1454        mShadowRadius = radius;
1455        mShadowDx = dx;
1456        mShadowDy = dy;
1457
1458        invalidate();
1459    }
1460
1461    /**
1462     * @return the base paint used for the text.  Please use this only to
1463     * consult the Paint's properties and not to change them.
1464     */
1465    public TextPaint getPaint() {
1466        return mTextPaint;
1467    }
1468
1469    /**
1470     * Sets the autolink mask of the text.  See {@link
1471     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1472     * possible values.
1473     *
1474     * @attr ref android.R.styleable#TextView_autoLink
1475     */
1476    public final void setAutoLinkMask(int mask) {
1477        mAutoLinkMask = mask;
1478    }
1479
1480    /**
1481     * Sets whether the movement method will automatically be set to
1482     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1483     * set to nonzero and links are detected in {@link #setText}.
1484     * The default is true.
1485     *
1486     * @attr ref android.R.styleable#TextView_linksClickable
1487     */
1488    public final void setLinksClickable(boolean whether) {
1489        mLinksClickable = whether;
1490    }
1491
1492    /**
1493     * Returns whether the movement method will automatically be set to
1494     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1495     * set to nonzero and links are detected in {@link #setText}.
1496     * The default is true.
1497     *
1498     * @attr ref android.R.styleable#TextView_linksClickable
1499     */
1500    public final boolean getLinksClickable() {
1501        return mLinksClickable;
1502    }
1503
1504    /**
1505     * Returns the list of URLSpans attached to the text
1506     * (by {@link Linkify} or otherwise) if any.  You can call
1507     * {@link URLSpan#getURL} on them to find where they link to
1508     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
1509     * to find the region of the text they are attached to.
1510     */
1511    public URLSpan[] getUrls() {
1512        if (mText instanceof Spanned) {
1513            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
1514        } else {
1515            return new URLSpan[0];
1516        }
1517    }
1518
1519    /**
1520     * Sets the color of the hint text.
1521     *
1522     * @attr ref android.R.styleable#TextView_textColorHint
1523     */
1524    public final void setHintTextColor(int color) {
1525        mHintTextColor = ColorStateList.valueOf(color);
1526        updateTextColors();
1527    }
1528
1529    /**
1530     * Sets the color of the hint text.
1531     *
1532     * @attr ref android.R.styleable#TextView_textColorHint
1533     */
1534    public final void setHintTextColor(ColorStateList colors) {
1535        mHintTextColor = colors;
1536        updateTextColors();
1537    }
1538
1539    /**
1540     * <p>Return the color used to paint the hint text.</p>
1541     *
1542     * @return Returns the list of hint text colors.
1543     */
1544    public final ColorStateList getHintTextColors() {
1545        return mHintTextColor;
1546    }
1547
1548    /**
1549     * <p>Return the current color selected to paint the hint text.</p>
1550     *
1551     * @return Returns the current hint text color.
1552     */
1553    public final int getCurrentHintTextColor() {
1554        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
1555    }
1556
1557    /**
1558     * Sets the color of links in the text.
1559     *
1560     * @attr ref android.R.styleable#TextView_textColorLink
1561     */
1562    public final void setLinkTextColor(int color) {
1563        mLinkTextColor = ColorStateList.valueOf(color);
1564        updateTextColors();
1565    }
1566
1567    /**
1568     * Sets the color of links in the text.
1569     *
1570     * @attr ref android.R.styleable#TextView_textColorLink
1571     */
1572    public final void setLinkTextColor(ColorStateList colors) {
1573        mLinkTextColor = colors;
1574        updateTextColors();
1575    }
1576
1577    /**
1578     * <p>Returns the color used to paint links in the text.</p>
1579     *
1580     * @return Returns the list of link text colors.
1581     */
1582    public final ColorStateList getLinkTextColors() {
1583        return mLinkTextColor;
1584    }
1585
1586    /**
1587     * Sets the horizontal alignment of the text and the
1588     * vertical gravity that will be used when there is extra space
1589     * in the TextView beyond what is required for the text itself.
1590     *
1591     * @see android.view.Gravity
1592     * @attr ref android.R.styleable#TextView_gravity
1593     */
1594    public void setGravity(int gravity) {
1595        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
1596            gravity |= Gravity.LEFT;
1597        }
1598        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
1599            gravity |= Gravity.TOP;
1600        }
1601
1602        boolean newLayout = false;
1603
1604        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
1605            (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
1606            newLayout = true;
1607        }
1608
1609        if (gravity != mGravity) {
1610            invalidate();
1611        }
1612
1613        mGravity = gravity;
1614
1615        if (mLayout != null && newLayout) {
1616            // XXX this is heavy-handed because no actual content changes.
1617            int want = mLayout.getWidth();
1618            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
1619
1620            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
1621                          mRight - mLeft - getCompoundPaddingLeft() -
1622                          getCompoundPaddingRight(), true);
1623        }
1624    }
1625
1626    /**
1627     * Returns the horizontal and vertical alignment of this TextView.
1628     *
1629     * @see android.view.Gravity
1630     * @attr ref android.R.styleable#TextView_gravity
1631     */
1632    public int getGravity() {
1633        return mGravity;
1634    }
1635
1636    /**
1637     * @return the flags on the Paint being used to display the text.
1638     * @see Paint#getFlags
1639     */
1640    public int getPaintFlags() {
1641        return mTextPaint.getFlags();
1642    }
1643
1644    /**
1645     * Sets flags on the Paint being used to display the text and
1646     * reflows the text if they are different from the old flags.
1647     * @see Paint#setFlags
1648     */
1649    public void setPaintFlags(int flags) {
1650        if (mTextPaint.getFlags() != flags) {
1651            mTextPaint.setFlags(flags);
1652
1653            if (mLayout != null) {
1654                nullLayouts();
1655                requestLayout();
1656                invalidate();
1657            }
1658        }
1659    }
1660
1661    /**
1662     * Sets whether the text should be allowed to be wider than the
1663     * View is.  If false, it will be wrapped to the width of the View.
1664     *
1665     * @attr ref android.R.styleable#TextView_scrollHorizontally
1666     */
1667    public void setHorizontallyScrolling(boolean whether) {
1668        mHorizontallyScrolling = whether;
1669
1670        if (mLayout != null) {
1671            nullLayouts();
1672            requestLayout();
1673            invalidate();
1674        }
1675    }
1676
1677    /**
1678     * Makes the TextView at least this many lines tall
1679     *
1680     * @attr ref android.R.styleable#TextView_minLines
1681     */
1682    public void setMinLines(int minlines) {
1683        mMinimum = minlines;
1684        mMinMode = LINES;
1685
1686        requestLayout();
1687        invalidate();
1688    }
1689
1690    /**
1691     * Makes the TextView at least this many pixels tall
1692     *
1693     * @attr ref android.R.styleable#TextView_minHeight
1694     */
1695    public void setMinHeight(int minHeight) {
1696        mMinimum = minHeight;
1697        mMinMode = PIXELS;
1698
1699        requestLayout();
1700        invalidate();
1701    }
1702
1703    /**
1704     * Makes the TextView at most this many lines tall
1705     *
1706     * @attr ref android.R.styleable#TextView_maxLines
1707     */
1708    public void setMaxLines(int maxlines) {
1709        mMaximum = maxlines;
1710        mMaxMode = LINES;
1711
1712        requestLayout();
1713        invalidate();
1714    }
1715
1716    /**
1717     * Makes the TextView at most this many pixels tall
1718     *
1719     * @attr ref android.R.styleable#TextView_maxHeight
1720     */
1721    public void setMaxHeight(int maxHeight) {
1722        mMaximum = maxHeight;
1723        mMaxMode = PIXELS;
1724
1725        requestLayout();
1726        invalidate();
1727    }
1728
1729    /**
1730     * Makes the TextView exactly this many lines tall
1731     *
1732     * @attr ref android.R.styleable#TextView_lines
1733     */
1734    public void setLines(int lines) {
1735        mMaximum = mMinimum = lines;
1736        mMaxMode = mMinMode = LINES;
1737
1738        requestLayout();
1739        invalidate();
1740    }
1741
1742    /**
1743     * Makes the TextView exactly this many pixels tall.
1744     * You could do the same thing by specifying this number in the
1745     * LayoutParams.
1746     *
1747     * @attr ref android.R.styleable#TextView_height
1748     */
1749    public void setHeight(int pixels) {
1750        mMaximum = mMinimum = pixels;
1751        mMaxMode = mMinMode = PIXELS;
1752
1753        requestLayout();
1754        invalidate();
1755    }
1756
1757    /**
1758     * Makes the TextView at least this many ems wide
1759     *
1760     * @attr ref android.R.styleable#TextView_minEms
1761     */
1762    public void setMinEms(int minems) {
1763        mMinWidth = minems;
1764        mMinWidthMode = EMS;
1765
1766        requestLayout();
1767        invalidate();
1768    }
1769
1770    /**
1771     * Makes the TextView at least this many pixels wide
1772     *
1773     * @attr ref android.R.styleable#TextView_minWidth
1774     */
1775    public void setMinWidth(int minpixels) {
1776        mMinWidth = minpixels;
1777        mMinWidthMode = PIXELS;
1778
1779        requestLayout();
1780        invalidate();
1781    }
1782
1783    /**
1784     * Makes the TextView at most this many ems wide
1785     *
1786     * @attr ref android.R.styleable#TextView_maxEms
1787     */
1788    public void setMaxEms(int maxems) {
1789        mMaxWidth = maxems;
1790        mMaxWidthMode = EMS;
1791
1792        requestLayout();
1793        invalidate();
1794    }
1795
1796    /**
1797     * Makes the TextView at most this many pixels wide
1798     *
1799     * @attr ref android.R.styleable#TextView_maxWidth
1800     */
1801    public void setMaxWidth(int maxpixels) {
1802        mMaxWidth = maxpixels;
1803        mMaxWidthMode = PIXELS;
1804
1805        requestLayout();
1806        invalidate();
1807    }
1808
1809    /**
1810     * Makes the TextView exactly this many ems wide
1811     *
1812     * @attr ref android.R.styleable#TextView_ems
1813     */
1814    public void setEms(int ems) {
1815        mMaxWidth = mMinWidth = ems;
1816        mMaxWidthMode = mMinWidthMode = EMS;
1817
1818        requestLayout();
1819        invalidate();
1820    }
1821
1822    /**
1823     * Makes the TextView exactly this many pixels wide.
1824     * You could do the same thing by specifying this number in the
1825     * LayoutParams.
1826     *
1827     * @attr ref android.R.styleable#TextView_width
1828     */
1829    public void setWidth(int pixels) {
1830        mMaxWidth = mMinWidth = pixels;
1831        mMaxWidthMode = mMinWidthMode = PIXELS;
1832
1833        requestLayout();
1834        invalidate();
1835    }
1836
1837
1838    /**
1839     * Sets line spacing for this TextView.  Each line will have its height
1840     * multiplied by <code>mult</code> and have <code>add</code> added to it.
1841     *
1842     * @attr ref android.R.styleable#TextView_lineSpacingExtra
1843     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
1844     */
1845    public void setLineSpacing(float add, float mult) {
1846        mSpacingMult = mult;
1847        mSpacingAdd = add;
1848
1849        if (mLayout != null) {
1850            nullLayouts();
1851            requestLayout();
1852            invalidate();
1853        }
1854    }
1855
1856    /**
1857     * Convenience method: Append the specified text to the TextView's
1858     * display buffer, upgrading it to BufferType.EDITABLE if it was
1859     * not already editable.
1860     */
1861    public final void append(CharSequence text) {
1862        append(text, 0, text.length());
1863    }
1864
1865    /**
1866     * Convenience method: Append the specified text slice to the TextView's
1867     * display buffer, upgrading it to BufferType.EDITABLE if it was
1868     * not already editable.
1869     */
1870    public void append(CharSequence text, int start, int end) {
1871        if (!(mText instanceof Editable)) {
1872            setText(mText, BufferType.EDITABLE);
1873        }
1874
1875        ((Editable) mText).append(text, start, end);
1876    }
1877
1878    private void updateTextColors() {
1879        boolean inval = false;
1880        int color = mTextColor.getColorForState(getDrawableState(), 0);
1881        if (color != mCurTextColor) {
1882            mCurTextColor = color;
1883            inval = true;
1884        }
1885        if (mLinkTextColor != null) {
1886            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
1887            if (color != mTextPaint.linkColor) {
1888                mTextPaint.linkColor = color;
1889                inval = true;
1890            }
1891        }
1892        if (mHintTextColor != null) {
1893            color = mHintTextColor.getColorForState(getDrawableState(), 0);
1894            if (color != mCurHintTextColor && mText.length() == 0) {
1895                mCurHintTextColor = color;
1896                inval = true;
1897            }
1898        }
1899        if (inval) {
1900            invalidate();
1901        }
1902    }
1903
1904    @Override
1905    protected void drawableStateChanged() {
1906        super.drawableStateChanged();
1907        if (mTextColor != null && mTextColor.isStateful()
1908                || (mHintTextColor != null && mHintTextColor.isStateful())
1909                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
1910            updateTextColors();
1911        }
1912
1913        int[] state = getDrawableState();
1914        if (mDrawableTop != null && mDrawableTop.isStateful()) {
1915            mDrawableTop.setState(state);
1916        }
1917        if (mDrawableBottom != null && mDrawableBottom.isStateful()) {
1918            mDrawableBottom.setState(state);
1919        }
1920        if (mDrawableLeft != null && mDrawableLeft.isStateful()) {
1921            mDrawableLeft.setState(state);
1922        }
1923        if (mDrawableRight != null && mDrawableRight.isStateful()) {
1924            mDrawableRight.setState(state);
1925        }
1926    }
1927
1928    /**
1929     * User interface state that is stored by TextView for implementing
1930     * {@link View#onSaveInstanceState}.
1931     */
1932    public static class SavedState extends BaseSavedState {
1933        int selStart;
1934        int selEnd;
1935        CharSequence text;
1936        boolean frozenWithFocus;
1937
1938        SavedState(Parcelable superState) {
1939            super(superState);
1940        }
1941
1942        @Override
1943        public void writeToParcel(Parcel out, int flags) {
1944            super.writeToParcel(out, flags);
1945            out.writeInt(selStart);
1946            out.writeInt(selEnd);
1947            out.writeInt(frozenWithFocus ? 1 : 0);
1948            TextUtils.writeToParcel(text, out, flags);
1949        }
1950
1951        @Override
1952        public String toString() {
1953            String str = "TextView.SavedState{"
1954                    + Integer.toHexString(System.identityHashCode(this))
1955                    + " start=" + selStart + " end=" + selEnd;
1956            if (text != null) {
1957                str += " text=" + text;
1958            }
1959            return str + "}";
1960        }
1961
1962        public static final Parcelable.Creator<SavedState> CREATOR
1963                = new Parcelable.Creator<SavedState>() {
1964            public SavedState createFromParcel(Parcel in) {
1965                return new SavedState(in);
1966            }
1967
1968            public SavedState[] newArray(int size) {
1969                return new SavedState[size];
1970            }
1971        };
1972
1973        private SavedState(Parcel in) {
1974            super(in);
1975            selStart = in.readInt();
1976            selEnd = in.readInt();
1977            frozenWithFocus = (in.readInt() != 0);
1978            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1979        }
1980    }
1981
1982    @Override
1983    public Parcelable onSaveInstanceState() {
1984        Parcelable superState = super.onSaveInstanceState();
1985
1986        // Save state if we are forced to
1987        boolean save = mFreezesText;
1988        int start = 0;
1989        int end = 0;
1990
1991        if (mText != null) {
1992            start = Selection.getSelectionStart(mText);
1993            end = Selection.getSelectionEnd(mText);
1994            if (start >= 0 || end >= 0) {
1995                // Or save state if there is a selection
1996                save = true;
1997            }
1998        }
1999
2000        if (save) {
2001            SavedState ss = new SavedState(superState);
2002            // XXX Should also save the current scroll position!
2003            ss.selStart = start;
2004            ss.selEnd = end;
2005
2006            if (mText instanceof Spanned) {
2007                /*
2008                 * Calling setText() strips off any ChangeWatchers;
2009                 * strip them now to avoid leaking references.
2010                 * But do it to a copy so that if there are any
2011                 * further changes to the text of this view, it
2012                 * won't get into an inconsistent state.
2013                 */
2014
2015                Spannable sp = new SpannableString(mText);
2016
2017                for (ChangeWatcher cw :
2018                     sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2019                    sp.removeSpan(cw);
2020                }
2021
2022                ss.text = sp;
2023            } else {
2024                ss.text = mText.toString();
2025            }
2026
2027            if (isFocused() && start >= 0 && end >= 0) {
2028                ss.frozenWithFocus = true;
2029            }
2030
2031            return ss;
2032        }
2033
2034        return null;
2035    }
2036
2037    @Override
2038    public void onRestoreInstanceState(Parcelable state) {
2039        SavedState ss = (SavedState)state;
2040        super.onRestoreInstanceState(ss.getSuperState());
2041
2042        // XXX restore buffer type too, as well as lots of other stuff
2043        if (ss.text != null) {
2044            setText(ss.text);
2045        }
2046
2047        if (ss.selStart >= 0 && ss.selEnd >= 0) {
2048            if (mText instanceof Spannable) {
2049                int len = mText.length();
2050
2051                if (ss.selStart > len || ss.selEnd > len) {
2052                    String restored = "";
2053
2054                    if (ss.text != null) {
2055                        restored = "(restored) ";
2056                    }
2057
2058                    Log.e("TextView", "Saved cursor position " + ss.selStart +
2059                          "/" + ss.selEnd + " out of range for " + restored +
2060                          "text " + mText);
2061                } else {
2062                    Selection.setSelection((Spannable) mText, ss.selStart,
2063                                           ss.selEnd);
2064
2065                    if (ss.frozenWithFocus) {
2066                        mFrozenWithFocus = true;
2067                    }
2068                }
2069            }
2070        }
2071    }
2072
2073    /**
2074     * Control whether this text view saves its entire text contents when
2075     * freezing to an icicle, in addition to dynamic state such as cursor
2076     * position.  By default this is false, not saving the text.  Set to true
2077     * if the text in the text view is not being saved somewhere else in
2078     * persistent storage (such as in a content provider) so that if the
2079     * view is later thawed the user will not lose their data.
2080     *
2081     * @param freezesText Controls whether a frozen icicle should include the
2082     * entire text data: true to include it, false to not.
2083     *
2084     * @attr ref android.R.styleable#TextView_freezesText
2085     */
2086    public void setFreezesText(boolean freezesText) {
2087        mFreezesText = freezesText;
2088    }
2089
2090    /**
2091     * Return whether this text view is including its entire text contents
2092     * in frozen icicles.
2093     *
2094     * @return Returns true if text is included, false if it isn't.
2095     *
2096     * @see #setFreezesText
2097     */
2098    public boolean getFreezesText() {
2099        return mFreezesText;
2100    }
2101
2102    ///////////////////////////////////////////////////////////////////////////
2103
2104    /**
2105     * Sets the Factory used to create new Editables.
2106     */
2107    public final void setEditableFactory(Editable.Factory factory) {
2108        mEditableFactory = factory;
2109        setText(mText);
2110    }
2111
2112    /**
2113     * Sets the Factory used to create new Spannables.
2114     */
2115    public final void setSpannableFactory(Spannable.Factory factory) {
2116        mSpannableFactory = factory;
2117        setText(mText);
2118    }
2119
2120    /**
2121     * Sets the string value of the TextView. TextView <em>does not</em> accept
2122     * HTML-like formatting, which you can do with text strings in XML resource files.
2123     * To style your strings, attach android.text.style.* objects to a
2124     * {@link android.text.SpannableString SpannableString}, or see
2125     * <a href="{@docRoot}reference/available-resources.html#stringresources">
2126     * String Resources</a> for an example of setting formatted text in the XML resource file.
2127     *
2128     * @attr ref android.R.styleable#TextView_text
2129     */
2130    public final void setText(CharSequence text) {
2131        setText(text, mBufferType);
2132    }
2133
2134    /**
2135     * Like {@link #setText(CharSequence)},
2136     * except that the cursor position (if any) is retained in the new text.
2137     *
2138     * @param text The new text to place in the text view.
2139     *
2140     * @see #setText(CharSequence)
2141     */
2142    public final void setTextKeepState(CharSequence text) {
2143        setTextKeepState(text, mBufferType);
2144    }
2145
2146    /**
2147     * Sets the text that this TextView is to display (see
2148     * {@link #setText(CharSequence)}) and also sets whether it is stored
2149     * in a styleable/spannable buffer and whether it is editable.
2150     *
2151     * @attr ref android.R.styleable#TextView_text
2152     * @attr ref android.R.styleable#TextView_bufferType
2153     */
2154    public void setText(CharSequence text, BufferType type) {
2155        setText(text, type, true, 0);
2156
2157        if (mCharWrapper != null) {
2158            mCharWrapper.mChars = null;
2159        }
2160    }
2161
2162    private void setText(CharSequence text, BufferType type,
2163                         boolean notifyBefore, int oldlen) {
2164        if (text == null) {
2165            text = "";
2166        }
2167
2168        int n = mFilters.length;
2169        for (int i = 0; i < n; i++) {
2170            CharSequence out = mFilters[i].filter(text, 0, text.length(),
2171                                                  EMPTY_SPANNED, 0, 0);
2172            if (out != null) {
2173                text = out;
2174            }
2175        }
2176
2177        if (notifyBefore) {
2178            if (mText != null) {
2179                oldlen = mText.length();
2180                sendBeforeTextChanged(mText, 0, oldlen, text.length());
2181            } else {
2182                sendBeforeTextChanged("", 0, 0, text.length());
2183            }
2184        }
2185
2186        if (type == BufferType.EDITABLE || mInput != null) {
2187            Editable t = mEditableFactory.newEditable(text);
2188            text = t;
2189
2190            setFilters(t, mFilters);
2191        } else if (type == BufferType.SPANNABLE || mMovement != null) {
2192            text = mSpannableFactory.newSpannable(text);
2193        } else if (!(text instanceof CharWrapper)) {
2194            text = TextUtils.stringOrSpannedString(text);
2195        }
2196
2197        if (mAutoLinkMask != 0) {
2198            Spannable s2;
2199
2200            if (type == BufferType.EDITABLE || text instanceof Spannable) {
2201                s2 = (Spannable) text;
2202            } else {
2203                s2 = mSpannableFactory.newSpannable(text);
2204            }
2205
2206            if (Linkify.addLinks(s2, mAutoLinkMask)) {
2207                text = s2;
2208                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
2209
2210                /*
2211                 * We must go ahead and set the text before changing the
2212                 * movement method, because setMovementMethod() may call
2213                 * setText() again to try to upgrade the buffer type.
2214                 */
2215                mText = text;
2216
2217                if (mLinksClickable) {
2218                    setMovementMethod(LinkMovementMethod.getInstance());
2219                }
2220            }
2221        }
2222
2223        mBufferType = type;
2224        mText = text;
2225
2226        if (mTransformation == null)
2227            mTransformed = text;
2228        else
2229            mTransformed = mTransformation.getTransformation(text, this);
2230
2231        final int textLength = text.length();
2232
2233        if (text instanceof Spannable) {
2234            Spannable sp = (Spannable) text;
2235
2236            // Remove any ChangeWatchers that might have come
2237            // from other TextViews.
2238            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
2239            final int count = watchers.length;
2240            for (int i = 0; i < count; i++)
2241                sp.removeSpan(watchers[i]);
2242
2243            if (mChangeWatcher == null)
2244                mChangeWatcher = new ChangeWatcher();
2245
2246            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
2247                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
2248
2249            if (mInput != null) {
2250                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2251            }
2252
2253            if (mTransformation != null) {
2254                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2255
2256            }
2257
2258            if (mMovement != null) {
2259                mMovement.initialize(this, (Spannable) text);
2260
2261                /*
2262                 * Initializing the movement method will have set the
2263                 * selection, so reset mSelectionMoved to keep that from
2264                 * interfering with the normal on-focus selection-setting.
2265                 */
2266                mSelectionMoved = false;
2267            }
2268        }
2269
2270        if (mLayout != null) {
2271            checkForRelayout();
2272        }
2273
2274        sendOnTextChanged(text, 0, oldlen, textLength);
2275        onTextChanged(text, 0, oldlen, textLength);
2276    }
2277
2278    /**
2279     * Sets the TextView to display the specified slice of the specified
2280     * char array.  You must promise that you will not change the contents
2281     * of the array except for right before another call to setText(),
2282     * since the TextView has no way to know that the text
2283     * has changed and that it needs to invalidate and re-layout.
2284     */
2285    public final void setText(char[] text, int start, int len) {
2286        int oldlen = 0;
2287
2288        if (start < 0 || len < 0 || start + len > text.length) {
2289            throw new IndexOutOfBoundsException(start + ", " + len);
2290        }
2291
2292        /*
2293         * We must do the before-notification here ourselves because if
2294         * the old text is a CharWrapper we destroy it before calling
2295         * into the normal path.
2296         */
2297        if (mText != null) {
2298            oldlen = mText.length();
2299            sendBeforeTextChanged(mText, 0, oldlen, len);
2300        } else {
2301            sendBeforeTextChanged("", 0, 0, len);
2302        }
2303
2304        if (mCharWrapper == null) {
2305            mCharWrapper = new CharWrapper(text, start, len);
2306        } else {
2307            mCharWrapper.set(text, start, len);
2308        }
2309
2310        setText(mCharWrapper, mBufferType, false, oldlen);
2311    }
2312
2313    private static class CharWrapper
2314            implements CharSequence, GetChars, GraphicsOperations {
2315        private char[] mChars;
2316        private int mStart, mLength;
2317
2318        public CharWrapper(char[] chars, int start, int len) {
2319            mChars = chars;
2320            mStart = start;
2321            mLength = len;
2322        }
2323
2324        /* package */ void set(char[] chars, int start, int len) {
2325            mChars = chars;
2326            mStart = start;
2327            mLength = len;
2328        }
2329
2330        public int length() {
2331            return mLength;
2332        }
2333
2334        public char charAt(int off) {
2335            return mChars[off + mStart];
2336        }
2337
2338        public String toString() {
2339            return new String(mChars, mStart, mLength);
2340        }
2341
2342        public CharSequence subSequence(int start, int end) {
2343            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2344                throw new IndexOutOfBoundsException(start + ", " + end);
2345            }
2346
2347            return new String(mChars, start + mStart, end - start);
2348        }
2349
2350        public void getChars(int start, int end, char[] buf, int off) {
2351            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2352                throw new IndexOutOfBoundsException(start + ", " + end);
2353            }
2354
2355            System.arraycopy(mChars, start + mStart, buf, off, end - start);
2356        }
2357
2358        public void drawText(Canvas c, int start, int end,
2359                             float x, float y, Paint p) {
2360            c.drawText(mChars, start + mStart, end - start, x, y, p);
2361        }
2362
2363        public float measureText(int start, int end, Paint p) {
2364            return p.measureText(mChars, start + mStart, end - start);
2365        }
2366
2367        public int getTextWidths(int start, int end, float[] widths, Paint p) {
2368            return p.getTextWidths(mChars, start + mStart, end - start, widths);
2369        }
2370    }
2371
2372    /**
2373     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2374     * except that the cursor position (if any) is retained in the new text.
2375     *
2376     * @see #setText(CharSequence, android.widget.TextView.BufferType)
2377     */
2378    public final void setTextKeepState(CharSequence text, BufferType type) {
2379        int start = getSelectionStart();
2380        int end = getSelectionEnd();
2381        int len = text.length();
2382
2383        setText(text, type);
2384
2385        if (start >= 0 || end >= 0) {
2386            if (mText instanceof Spannable) {
2387                Selection.setSelection((Spannable) mText,
2388                                       Math.max(0, Math.min(start, len)),
2389                                       Math.max(0, Math.min(end, len)));
2390            }
2391        }
2392    }
2393
2394    public final void setText(int resid) {
2395        setText(getContext().getResources().getText(resid));
2396    }
2397
2398    public final void setText(int resid, BufferType type) {
2399        setText(getContext().getResources().getText(resid), type);
2400    }
2401
2402    /**
2403     * Sets the text to be displayed when the text of the TextView is empty.
2404     * Null means to use the normal empty text.  The hint does not
2405     * currently participate in determining the size of the view.
2406     *
2407     * @attr ref android.R.styleable#TextView_hint
2408     */
2409    public final void setHint(CharSequence hint) {
2410        mHint = TextUtils.stringOrSpannedString(hint);
2411
2412        if (mLayout != null) {
2413            checkForRelayout();
2414        }
2415
2416        if (mText.length() == 0)
2417            invalidate();
2418    }
2419
2420    /**
2421     * Sets the text to be displayed when the text of the TextView is empty,
2422     * from a resource.
2423     *
2424     * @attr ref android.R.styleable#TextView_hint
2425     */
2426    public final void setHint(int resid) {
2427        setHint(getContext().getResources().getText(resid));
2428    }
2429
2430    /**
2431     * Returns the hint that is displayed when the text of the TextView
2432     * is empty.
2433     *
2434     * @attr ref android.R.styleable#TextView_hint
2435     */
2436    public CharSequence getHint() {
2437        return mHint;
2438    }
2439
2440    /**
2441     * Returns the error message that was set to be displayed with
2442     * {@link #setError}, or <code>null</code> if no error was set
2443     * or if it the error was cleared by the widget after user input.
2444     */
2445    public CharSequence getError() {
2446        return mError;
2447    }
2448
2449    /**
2450     * Sets the right-hand compound drawable of the TextView to the "error"
2451     * icon and sets an error message that will be displayed in a popup when
2452     * the TextView has focus.  The icon and error message will be reset to
2453     * null when any key events cause changes to the TextView's text.  If the
2454     * <code>error</code> is <code>null</code>, the error message and icon
2455     * will be cleared.
2456     */
2457    public void setError(CharSequence error) {
2458        if (error == null) {
2459            setError(null, null);
2460        } else {
2461            Drawable dr = getContext().getResources().
2462                getDrawable(com.android.internal.R.drawable.
2463                            indicator_input_error);
2464
2465            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
2466            setError(error, dr);
2467        }
2468    }
2469
2470    /**
2471     * Sets the right-hand compound drawable of the TextView to the specified
2472     * icon and sets an error message that will be displayed in a popup when
2473     * the TextView has focus.  The icon and error message will be reset to
2474     * null when any key events cause changes to the TextView's text.  The
2475     * drawable must already have had {@link Drawable#setBounds} set on it.
2476     * If the <code>error</code> is <code>null</code>, the error message will
2477     * be cleared (and you should provide a <code>null</code> icon as well).
2478     */
2479    public void setError(CharSequence error, Drawable icon) {
2480        error = TextUtils.stringOrSpannedString(error);
2481
2482        mError = error;
2483        mErrorWasChanged = true;
2484        setCompoundDrawables(mDrawableLeft, mDrawableTop,
2485                             icon, mDrawableBottom);
2486
2487        if (error == null) {
2488            if (mPopup != null) {
2489                if (mPopup.isShowing()) {
2490                    mPopup.dismiss();
2491                }
2492
2493                mPopup = null;
2494            }
2495        } else {
2496            if (isFocused()) {
2497                showError();
2498            }
2499        }
2500    }
2501
2502    private void showError() {
2503        if (mPopup == null) {
2504            LayoutInflater inflater = LayoutInflater.from(getContext());
2505            TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
2506                    null);
2507
2508            mPopup = new PopupWindow(err, 200, 50);
2509            mPopup.setFocusable(false);
2510        }
2511
2512        TextView tv = (TextView) mPopup.getContentView();
2513        chooseSize(mPopup, mError, tv);
2514        tv.setText(mError);
2515
2516        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
2517    }
2518
2519    /**
2520     * Returns the Y offset to make the pointy top of the error point
2521     * at the middle of the error icon.
2522     */
2523    private int getErrorX() {
2524        /*
2525         * The "25" is the distance between the point and the right edge
2526         * of the background
2527         */
2528
2529        return getWidth() - mPopup.getWidth()
2530                - getPaddingRight() - mDrawableSizeRight / 2 + 25;
2531    }
2532
2533    /**
2534     * Returns the Y offset to make the pointy top of the error point
2535     * at the bottom of the error icon.
2536     */
2537    private int getErrorY() {
2538        /*
2539         * Compound, not extended, because the icon is not clipped
2540         * if the text height is smaller.
2541         */
2542        int vspace = mBottom - mTop -
2543                     getCompoundPaddingBottom() - getCompoundPaddingTop();
2544
2545        int icontop = getCompoundPaddingTop() +
2546                                 (vspace - mDrawableHeightRight) / 2;
2547
2548        /*
2549         * The "2" is the distance between the point and the top edge
2550         * of the background.
2551         */
2552
2553        return icontop + mDrawableHeightRight - getHeight() - 2;
2554    }
2555
2556    private void hideError() {
2557        if (mPopup != null) {
2558            if (mPopup.isShowing()) {
2559                mPopup.dismiss();
2560            }
2561        }
2562    }
2563
2564    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
2565        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
2566        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
2567
2568        /*
2569         * Figure out how big the text would be if we laid it out to the
2570         * full width of this view minus the border.
2571         */
2572        int cap = getWidth() - wid;
2573        if (cap < 0) {
2574            cap = 200; // We must not be measured yet -- setFrame() will fix it.
2575        }
2576
2577        Layout l = new StaticLayout(text, tv.getPaint(), cap,
2578                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
2579        float max = 0;
2580        for (int i = 0; i < l.getLineCount(); i++) {
2581            max = Math.max(max, l.getLineWidth(i));
2582        }
2583
2584        /*
2585         * Now set the popup size to be big enough for the text plus the border.
2586         */
2587        pop.setWidth(wid + (int) Math.ceil(max));
2588        pop.setHeight(ht + l.getHeight());
2589    }
2590
2591
2592    @Override
2593    protected boolean setFrame(int l, int t, int r, int b) {
2594        boolean result = super.setFrame(l, t, r, b);
2595
2596        if (mPopup != null) {
2597            TextView tv = (TextView) mPopup.getContentView();
2598            chooseSize(mPopup, mError, tv);
2599            mPopup.update(this, getErrorX(), getErrorY(), -1, -1);
2600        }
2601
2602        return result;
2603    }
2604
2605    /**
2606     * Sets the list of input filters that will be used if the buffer is
2607     * Editable.  Has no effect otherwise.
2608     *
2609     * @attr ref android.R.styleable#TextView_maxLength
2610     */
2611    public void setFilters(InputFilter[] filters) {
2612        if (filters == null) {
2613            throw new IllegalArgumentException();
2614        }
2615
2616        mFilters = filters;
2617
2618        if (mText instanceof Editable) {
2619            setFilters((Editable) mText, filters);
2620        }
2621    }
2622
2623    /**
2624     * Sets the list of input filters on the specified Editable,
2625     * and includes mInput in the list if it is an InputFilter.
2626     */
2627    private void setFilters(Editable e, InputFilter[] filters) {
2628        if (mInput instanceof InputFilter) {
2629            InputFilter[] nf = new InputFilter[filters.length + 1];
2630
2631            System.arraycopy(filters, 0, nf, 0, filters.length);
2632            nf[filters.length] = (InputFilter) mInput;
2633
2634            e.setFilters(nf);
2635        } else {
2636            e.setFilters(filters);
2637        }
2638    }
2639
2640    /**
2641     * Returns the current list of input filters.
2642     */
2643    public InputFilter[] getFilters() {
2644        return mFilters;
2645    }
2646
2647    /////////////////////////////////////////////////////////////////////////
2648
2649    private int getVerticalOffset(boolean forceNormal) {
2650        int voffset = 0;
2651        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2652
2653        Layout l = mLayout;
2654        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
2655            l = mHintLayout;
2656        }
2657
2658        if (gravity != Gravity.TOP) {
2659            int boxht;
2660
2661            if (l == mHintLayout) {
2662                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
2663                        getCompoundPaddingBottom();
2664            } else {
2665                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
2666                        getExtendedPaddingBottom();
2667            }
2668            int textht = l.getHeight();
2669
2670            if (textht < boxht) {
2671                if (gravity == Gravity.BOTTOM)
2672                    voffset = boxht - textht;
2673                else // (gravity == Gravity.CENTER_VERTICAL)
2674                    voffset = (boxht - textht) >> 1;
2675            }
2676        }
2677        return voffset;
2678    }
2679
2680    private int getBottomVerticalOffset(boolean forceNormal) {
2681        int voffset = 0;
2682        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2683
2684        Layout l = mLayout;
2685        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
2686            l = mHintLayout;
2687        }
2688
2689        if (gravity != Gravity.BOTTOM) {
2690            int boxht;
2691
2692            if (l == mHintLayout) {
2693                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
2694                        getCompoundPaddingBottom();
2695            } else {
2696                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
2697                        getExtendedPaddingBottom();
2698            }
2699            int textht = l.getHeight();
2700
2701            if (textht < boxht) {
2702                if (gravity == Gravity.TOP)
2703                    voffset = boxht - textht;
2704                else // (gravity == Gravity.CENTER_VERTICAL)
2705                    voffset = (boxht - textht) >> 1;
2706            }
2707        }
2708        return voffset;
2709    }
2710
2711    private void invalidateCursorPath() {
2712        if (mHighlightPathBogus) {
2713            invalidateCursor();
2714        } else {
2715            synchronized (sTempRect) {
2716                mHighlightPath.computeBounds(sTempRect, false);
2717
2718                int left = getCompoundPaddingLeft();
2719                int top = getExtendedPaddingTop() + getVerticalOffset(true);
2720
2721                invalidate((int) sTempRect.left + left,
2722                           (int) sTempRect.top + top,
2723                           (int) sTempRect.right + left + 1,
2724                           (int) sTempRect.bottom + top + 1);
2725            }
2726        }
2727    }
2728
2729    private void invalidateCursor() {
2730        int where = Selection.getSelectionEnd(mText);
2731
2732        invalidateCursor(where, where, where);
2733    }
2734
2735    private void invalidateCursor(int a, int b, int c) {
2736        if (mLayout == null) {
2737            invalidate();
2738        } else {
2739            if (a >= 0 || b >= 0 || c >= 0) {
2740                int first = Math.min(Math.min(a, b), c);
2741                int last = Math.max(Math.max(a, b), c);
2742
2743                int line = mLayout.getLineForOffset(first);
2744                int top = mLayout.getLineTop(line);
2745
2746                // This is ridiculous, but the descent from the line above
2747                // can hang down into the line we really want to redraw,
2748                // so we have to invalidate part of the line above to make
2749                // sure everything that needs to be redrawn really is.
2750                // (But not the whole line above, because that would cause
2751                // the same problem with the descenders on the line above it!)
2752                if (line > 0) {
2753                    top -= mLayout.getLineDescent(line - 1);
2754                }
2755
2756                int line2;
2757
2758                if (first == last)
2759                    line2 = line;
2760                else
2761                    line2 = mLayout.getLineForOffset(last);
2762
2763                int bottom = mLayout.getLineTop(line2 + 1);
2764                int voffset = getVerticalOffset(true);
2765
2766                int left = getCompoundPaddingLeft() + mScrollX;
2767                invalidate(left, top + voffset + getExtendedPaddingTop(),
2768                           left + getWidth() - getCompoundPaddingLeft() -
2769                           getCompoundPaddingRight(),
2770                           bottom + voffset + getExtendedPaddingTop());
2771            }
2772        }
2773    }
2774
2775    private void registerForPreDraw() {
2776        final ViewTreeObserver observer = getViewTreeObserver();
2777        if (observer == null) {
2778            return;
2779        }
2780
2781        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
2782            observer.addOnPreDrawListener(this);
2783            mPreDrawState = PREDRAW_PENDING;
2784        } else if (mPreDrawState == PREDRAW_DONE) {
2785            mPreDrawState = PREDRAW_PENDING;
2786        }
2787
2788        // else state is PREDRAW_PENDING, so keep waiting.
2789    }
2790
2791    /**
2792     * {@inheritDoc}
2793     */
2794    public boolean onPreDraw() {
2795        if (mPreDrawState != PREDRAW_PENDING) {
2796            return true;
2797        }
2798
2799        if (mLayout == null) {
2800            assumeLayout();
2801        }
2802
2803        boolean changed = false;
2804
2805        if (mMovement != null) {
2806            int curs = Selection.getSelectionEnd(mText);
2807
2808            /*
2809             * TODO: This should really only keep the end in view if
2810             * it already was before the text changed.  I'm not sure
2811             * of a good way to tell from here if it was.
2812             */
2813            if (curs < 0 &&
2814                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
2815                curs = mText.length();
2816            }
2817
2818            if (curs >= 0) {
2819                changed = bringPointIntoView(curs);
2820            }
2821        } else {
2822            changed = bringTextIntoView();
2823        }
2824
2825        mPreDrawState = PREDRAW_DONE;
2826        return !changed;
2827    }
2828
2829    @Override
2830    protected void onDetachedFromWindow() {
2831        super.onDetachedFromWindow();
2832
2833        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
2834            final ViewTreeObserver observer = getViewTreeObserver();
2835            if (observer != null) {
2836                observer.removeOnPreDrawListener(this);
2837                mPreDrawState = PREDRAW_NOT_REGISTERED;
2838            }
2839        }
2840    }
2841
2842    @Override
2843    protected void onDraw(Canvas canvas) {
2844        // Draw the background for this view
2845        super.onDraw(canvas);
2846
2847        final int compoundPaddingLeft = getCompoundPaddingLeft();
2848        final int compoundPaddingTop = getCompoundPaddingTop();
2849        final int compoundPaddingRight = getCompoundPaddingRight();
2850        final int compoundPaddingBottom = getCompoundPaddingBottom();
2851        final int scrollX = mScrollX;
2852        final int scrollY = mScrollY;
2853        final int right = mRight;
2854        final int left = mLeft;
2855        final int bottom = mBottom;
2856        final int top = mTop;
2857
2858        if (mDrawables) {
2859            /*
2860             * Compound, not extended, because the icon is not clipped
2861             * if the text height is smaller.
2862             */
2863
2864            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
2865            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
2866
2867            if (mDrawableLeft != null) {
2868                canvas.save();
2869                canvas.translate(scrollX + mPaddingLeft,
2870                                 scrollY + compoundPaddingTop +
2871                                 (vspace - mDrawableHeightLeft) / 2);
2872                mDrawableLeft.draw(canvas);
2873                canvas.restore();
2874            }
2875
2876            if (mDrawableRight != null) {
2877                canvas.save();
2878                canvas.translate(scrollX + right - left - mPaddingRight - mDrawableSizeRight,
2879                         scrollY + compoundPaddingTop + (vspace - mDrawableHeightRight) / 2);
2880                mDrawableRight.draw(canvas);
2881                canvas.restore();
2882            }
2883
2884            if (mDrawableTop != null) {
2885                canvas.save();
2886                canvas.translate(scrollX + compoundPaddingLeft + (hspace - mDrawableWidthTop) / 2,
2887                        scrollY + mPaddingTop);
2888                mDrawableTop.draw(canvas);
2889                canvas.restore();
2890            }
2891
2892            if (mDrawableBottom != null) {
2893                canvas.save();
2894                canvas.translate(scrollX + compoundPaddingLeft +
2895                        (hspace - mDrawableWidthBottom) / 2,
2896                         scrollY + bottom - top - mPaddingBottom - mDrawableSizeBottom);
2897                mDrawableBottom.draw(canvas);
2898                canvas.restore();
2899            }
2900        }
2901
2902        if (mPreDrawState == PREDRAW_DONE) {
2903            final ViewTreeObserver observer = getViewTreeObserver();
2904            if (observer != null) {
2905                observer.removeOnPreDrawListener(this);
2906                mPreDrawState = PREDRAW_NOT_REGISTERED;
2907            }
2908        }
2909
2910        int color = mCurTextColor;
2911
2912        if (mLayout == null) {
2913            assumeLayout();
2914        }
2915
2916        Layout layout = mLayout;
2917        int cursorcolor = color;
2918
2919        if (mHint != null && mText.length() == 0) {
2920            if (mHintTextColor != null) {
2921                color = mCurHintTextColor;
2922            }
2923
2924            layout = mHintLayout;
2925        }
2926
2927        mTextPaint.setColor(color);
2928        mTextPaint.drawableState = getDrawableState();
2929
2930        canvas.save();
2931        /*  Would be faster if we didn't have to do this. Can we chop the
2932            (displayable) text so that we don't need to do this ever?
2933        */
2934
2935        int extendedPaddingTop = getExtendedPaddingTop();
2936        int extendedPaddingBottom = getExtendedPaddingBottom();
2937
2938        float clipLeft = compoundPaddingLeft + scrollX;
2939        float clipTop = extendedPaddingTop + scrollY;
2940        float clipRight = right - left - compoundPaddingRight + scrollX;
2941        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
2942
2943        if (mShadowRadius != 0) {
2944            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
2945            clipRight += Math.max(0, mShadowDx + mShadowRadius);
2946
2947            clipTop += Math.min(0, mShadowDy - mShadowRadius);
2948            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
2949        }
2950
2951        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
2952
2953        int voffsetText = 0;
2954        int voffsetCursor = 0;
2955
2956        // translate in by our padding
2957        {
2958            /* shortcircuit calling getVerticaOffset() */
2959            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
2960                voffsetText = getVerticalOffset(false);
2961                voffsetCursor = getVerticalOffset(true);
2962            }
2963            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
2964        }
2965
2966        Path highlight = null;
2967
2968        //  If there is no movement method, then there can be no selection.
2969        //  Check that first and attempt to skip everything having to do with
2970        //  the cursor.
2971        //  XXX This is not strictly true -- a program could set the
2972        //  selection manually if it really wanted to.
2973        if (mMovement != null && (isFocused() || isPressed())) {
2974            int start = Selection.getSelectionStart(mText);
2975            int end = Selection.getSelectionEnd(mText);
2976
2977            if (mCursorVisible && start >= 0 && isEnabled()) {
2978                if (mHighlightPath == null)
2979                    mHighlightPath = new Path();
2980
2981                if (start == end) {
2982                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
2983                        < BLINK) {
2984                        if (mHighlightPathBogus) {
2985                            mHighlightPath.reset();
2986                            mLayout.getCursorPath(start, mHighlightPath, mText);
2987                            mHighlightPathBogus = false;
2988                        }
2989
2990                        // XXX should pass to skin instead of drawing directly
2991                        mHighlightPaint.setColor(cursorcolor);
2992                        mHighlightPaint.setStyle(Paint.Style.STROKE);
2993
2994                        highlight = mHighlightPath;
2995                    }
2996                } else {
2997                    if (mHighlightPathBogus) {
2998                        mHighlightPath.reset();
2999                        mLayout.getSelectionPath(start, end, mHighlightPath);
3000                        mHighlightPathBogus = false;
3001                    }
3002
3003                    // XXX should pass to skin instead of drawing directly
3004                    mHighlightPaint.setColor(mHighlightColor);
3005                    mHighlightPaint.setStyle(Paint.Style.FILL);
3006
3007                    highlight = mHighlightPath;
3008                }
3009            }
3010        }
3011
3012        /*  Comment out until we decide what to do about animations
3013        boolean isLinearTextOn = false;
3014        if (currentTransformation != null) {
3015            isLinearTextOn = mTextPaint.isLinearTextOn();
3016            Matrix m = currentTransformation.getMatrix();
3017            if (!m.isIdentity()) {
3018                // mTextPaint.setLinearTextOn(true);
3019            }
3020        }
3021        */
3022
3023        layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
3024
3025        /*  Comment out until we decide what to do about animations
3026        if (currentTransformation != null) {
3027            mTextPaint.setLinearTextOn(isLinearTextOn);
3028        }
3029        */
3030
3031        canvas.restore();
3032    }
3033
3034    @Override
3035    public void getFocusedRect(Rect r) {
3036        if (mLayout == null) {
3037            super.getFocusedRect(r);
3038            return;
3039        }
3040
3041        int sel = getSelectionEnd();
3042        if (sel < 0) {
3043            super.getFocusedRect(r);
3044            return;
3045        }
3046
3047        int line = mLayout.getLineForOffset(sel);
3048        r.top = mLayout.getLineTop(line);
3049        r.bottom = mLayout.getLineBottom(line);
3050
3051        r.left = (int) mLayout.getPrimaryHorizontal(sel);
3052        r.right = r.left + 1;
3053    }
3054
3055    /**
3056     * Return the number of lines of text, or 0 if the internal Layout has not
3057     * been built.
3058     */
3059    public int getLineCount() {
3060        return mLayout != null ? mLayout.getLineCount() : 0;
3061    }
3062
3063    /**
3064     * Return the baseline for the specified line (0...getLineCount() - 1)
3065     * If bounds is not null, return the top, left, right, bottom extents
3066     * of the specified line in it. If the internal Layout has not been built,
3067     * return 0 and set bounds to (0, 0, 0, 0)
3068     * @param line which line to examine (0..getLineCount() - 1)
3069     * @param bounds Optional. If not null, it returns the extent of the line
3070     * @return the Y-coordinate of the baseline
3071     */
3072    public int getLineBounds(int line, Rect bounds) {
3073        if (mLayout == null) {
3074            if (bounds != null) {
3075                bounds.set(0, 0, 0, 0);
3076            }
3077            return 0;
3078        }
3079        else {
3080            int baseline = mLayout.getLineBounds(line, bounds);
3081
3082            int voffset = getExtendedPaddingTop();
3083            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3084                voffset += getVerticalOffset(true);
3085            }
3086            if (bounds != null) {
3087                bounds.offset(getCompoundPaddingLeft(), voffset);
3088            }
3089            return baseline + voffset;
3090        }
3091    }
3092
3093    @Override
3094    public int getBaseline() {
3095        if (mLayout == null) {
3096            return super.getBaseline();
3097        }
3098
3099        int voffset = 0;
3100        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3101            voffset = getVerticalOffset(true);
3102        }
3103
3104        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
3105    }
3106
3107    @Override
3108    public boolean onKeyDown(int keyCode, KeyEvent event) {
3109        if (!isEnabled()) {
3110            return super.onKeyDown(keyCode, event);
3111        }
3112
3113        switch (keyCode) {
3114            case KeyEvent.KEYCODE_DPAD_CENTER:
3115            case KeyEvent.KEYCODE_ENTER:
3116                if (mSingleLine && mInput != null) {
3117                    return super.onKeyDown(keyCode, event);
3118                }
3119        }
3120
3121        if (mInput != null) {
3122            /*
3123             * Keep track of what the error was before doing the input
3124             * so that if an input filter changed the error, we leave
3125             * that error showing.  Otherwise, we take down whatever
3126             * error was showing when the user types something.
3127             */
3128            mErrorWasChanged = false;
3129
3130            if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
3131                if (mError != null && !mErrorWasChanged) {
3132                    setError(null, null);
3133                }
3134                return true;
3135            }
3136        }
3137
3138        // bug 650865: sometimes we get a key event before a layout.
3139        // don't try to move around if we don't know the layout.
3140
3141        if (mMovement != null && mLayout != null)
3142            if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
3143                return true;
3144
3145        return super.onKeyDown(keyCode, event);
3146    }
3147
3148    @Override
3149    public boolean onKeyUp(int keyCode, KeyEvent event) {
3150        if (!isEnabled()) {
3151            return super.onKeyUp(keyCode, event);
3152        }
3153
3154        switch (keyCode) {
3155            case KeyEvent.KEYCODE_DPAD_CENTER:
3156            case KeyEvent.KEYCODE_ENTER:
3157                if (mSingleLine && mInput != null) {
3158                    /*
3159                     * If there is a click listener, just call through to
3160                     * super, which will invoke it.
3161                     *
3162                     * If there isn't a click listener, try to advance focus,
3163                     * but still call through to super, which will reset the
3164                     * pressed state and longpress state.  (It will also
3165                     * call performClick(), but that won't do anything in
3166                     * this case.)
3167                     */
3168                    if (mOnClickListener == null) {
3169                        View v = focusSearch(FOCUS_DOWN);
3170
3171                        if (v != null) {
3172                            if (!v.requestFocus(FOCUS_DOWN)) {
3173                                throw new IllegalStateException("focus search returned a view " +
3174                                        "that wasn't able to take focus!");
3175                            }
3176
3177                            /*
3178                             * Return true because we handled the key; super
3179                             * will return false because there was no click
3180                             * listener.
3181                             */
3182                            super.onKeyUp(keyCode, event);
3183                            return true;
3184                        }
3185                    }
3186
3187                    return super.onKeyUp(keyCode, event);
3188                }
3189        }
3190
3191        if (mInput != null)
3192            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
3193                return true;
3194
3195        if (mMovement != null && mLayout != null)
3196            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
3197                return true;
3198
3199        return super.onKeyUp(keyCode, event);
3200    }
3201
3202    private void nullLayouts() {
3203        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
3204            mSavedLayout = (BoringLayout) mLayout;
3205        }
3206        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
3207            mSavedHintLayout = (BoringLayout) mHintLayout;
3208        }
3209
3210        mLayout = mHintLayout = null;
3211    }
3212
3213    /**
3214     * Make a new Layout based on the already-measured size of the view,
3215     * on the assumption that it was measured correctly at some point.
3216     */
3217    private void assumeLayout() {
3218        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
3219
3220        if (width < 1) {
3221            width = 0;
3222        }
3223
3224        int physicalWidth = width;
3225
3226        if (mHorizontallyScrolling) {
3227            width = VERY_WIDE;
3228        }
3229
3230        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
3231                      physicalWidth, false);
3232    }
3233
3234    /**
3235     * The width passed in is now the desired layout width,
3236     * not the full view width with padding.
3237     * {@hide}
3238     */
3239    protected void makeNewLayout(int w, int hintWidth,
3240                                 BoringLayout.Metrics boring,
3241                                 BoringLayout.Metrics hintBoring,
3242                                 int ellipsisWidth, boolean bringIntoView) {
3243        mHighlightPathBogus = true;
3244
3245        if (w < 0) {
3246            w = 0;
3247        }
3248        if (hintWidth < 0) {
3249            hintWidth = 0;
3250        }
3251
3252        Layout.Alignment alignment;
3253        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
3254            case Gravity.CENTER_HORIZONTAL:
3255                alignment = Layout.Alignment.ALIGN_CENTER;
3256                break;
3257
3258            case Gravity.RIGHT:
3259                alignment = Layout.Alignment.ALIGN_OPPOSITE;
3260                break;
3261
3262            default:
3263                alignment = Layout.Alignment.ALIGN_NORMAL;
3264        }
3265
3266        if (mText instanceof Spannable) {
3267            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
3268                    alignment, mSpacingMult,
3269                    mSpacingAdd, mIncludePad, mEllipsize,
3270                    ellipsisWidth);
3271        } else {
3272            if (boring == UNKNOWN_BORING) {
3273                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
3274                                               mBoring);
3275                if (boring != null) {
3276                    mBoring = boring;
3277                }
3278            }
3279
3280            if (boring != null) {
3281                if (boring.width <= w &&
3282                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
3283                    if (mSavedLayout != null) {
3284                        mLayout = mSavedLayout.
3285                                replaceOrMake(mTransformed, mTextPaint,
3286                                w, alignment, mSpacingMult, mSpacingAdd,
3287                                boring, mIncludePad);
3288                    } else {
3289                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
3290                                w, alignment, mSpacingMult, mSpacingAdd,
3291                                boring, mIncludePad);
3292                    }
3293                    // Log.e("aaa", "Boring: " + mTransformed);
3294
3295                    mSavedLayout = (BoringLayout) mLayout;
3296                } else if (mEllipsize != null && boring.width <= w) {
3297                    if (mSavedLayout != null) {
3298                        mLayout = mSavedLayout.
3299                                replaceOrMake(mTransformed, mTextPaint,
3300                                w, alignment, mSpacingMult, mSpacingAdd,
3301                                boring, mIncludePad, mEllipsize,
3302                                ellipsisWidth);
3303                    } else {
3304                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
3305                                w, alignment, mSpacingMult, mSpacingAdd,
3306                                boring, mIncludePad, mEllipsize,
3307                                ellipsisWidth);
3308                    }
3309                } else if (mEllipsize != null) {
3310                    mLayout = new StaticLayout(mTransformed,
3311                                0, mTransformed.length(),
3312                                mTextPaint, w, alignment, mSpacingMult,
3313                                mSpacingAdd, mIncludePad, mEllipsize,
3314                                ellipsisWidth);
3315                } else {
3316                    mLayout = new StaticLayout(mTransformed, mTextPaint,
3317                            w, alignment, mSpacingMult, mSpacingAdd,
3318                            mIncludePad);
3319                    // Log.e("aaa", "Boring but wide: " + mTransformed);
3320                }
3321            } else if (mEllipsize != null) {
3322                mLayout = new StaticLayout(mTransformed,
3323                            0, mTransformed.length(),
3324                            mTextPaint, w, alignment, mSpacingMult,
3325                            mSpacingAdd, mIncludePad, mEllipsize,
3326                            ellipsisWidth);
3327            } else {
3328                mLayout = new StaticLayout(mTransformed, mTextPaint,
3329                        w, alignment, mSpacingMult, mSpacingAdd,
3330                        mIncludePad);
3331            }
3332        }
3333
3334        mHintLayout = null;
3335
3336        if (mHint != null) {
3337            if (hintBoring == UNKNOWN_BORING) {
3338                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
3339                                                   mHintBoring);
3340                if (hintBoring != null) {
3341                    mHintBoring = hintBoring;
3342                }
3343            }
3344
3345            if (hintBoring != null) {
3346                if (hintBoring.width <= hintWidth) {
3347                    if (mSavedHintLayout != null) {
3348                        mHintLayout = mSavedHintLayout.
3349                                replaceOrMake(mHint, mTextPaint,
3350                                hintWidth, alignment, mSpacingMult,
3351                                mSpacingAdd, hintBoring, mIncludePad);
3352                    } else {
3353                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
3354                                hintWidth, alignment, mSpacingMult,
3355                                mSpacingAdd, hintBoring, mIncludePad);
3356                    }
3357
3358                    mSavedHintLayout = (BoringLayout) mHintLayout;
3359                } else {
3360                    mHintLayout = new StaticLayout(mHint, mTextPaint,
3361                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
3362                            mIncludePad);
3363                }
3364            } else {
3365                mHintLayout = new StaticLayout(mHint, mTextPaint,
3366                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
3367                        mIncludePad);
3368            }
3369        }
3370
3371        if (bringIntoView) {
3372            registerForPreDraw();
3373        }
3374    }
3375
3376    private static int desired(Layout layout) {
3377        int n = layout.getLineCount();
3378        CharSequence text = layout.getText();
3379        float max = 0;
3380
3381        // if any line was wrapped, we can't use it.
3382        // but it's ok for the last line not to have a newline
3383
3384        for (int i = 0; i < n - 1; i++) {
3385            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
3386                return -1;
3387        }
3388
3389        for (int i = 0; i < n; i++) {
3390            max = Math.max(max, layout.getLineWidth(i));
3391        }
3392
3393        return (int) FloatMath.ceil(max);
3394    }
3395
3396    /**
3397     * Set whether the TextView includes extra top and bottom padding to make
3398     * room for accents that go above the normal ascent and descent.
3399     * The default is true.
3400     *
3401     * @attr ref android.R.styleable#TextView_includeFontPadding
3402     */
3403    public void setIncludeFontPadding(boolean includepad) {
3404        mIncludePad = includepad;
3405
3406        if (mLayout != null) {
3407            nullLayouts();
3408            requestLayout();
3409            invalidate();
3410        }
3411    }
3412
3413    private static final BoringLayout.Metrics UNKNOWN_BORING =
3414                                                new BoringLayout.Metrics();
3415
3416    @Override
3417    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3418        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
3419        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
3420        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
3421        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
3422
3423        int width;
3424        int height;
3425
3426        BoringLayout.Metrics boring = UNKNOWN_BORING;
3427        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
3428
3429        int des = -1;
3430        boolean fromexisting = false;
3431
3432        if (widthMode == MeasureSpec.EXACTLY) {
3433            // Parent has told us how big to be. So be it.
3434            width = widthSize;
3435        } else {
3436            if (mLayout != null && mEllipsize == null) {
3437                des = desired(mLayout);
3438            }
3439
3440            if (des < 0) {
3441                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
3442                                               mBoring);
3443                if (boring != null) {
3444                    mBoring = boring;
3445                }
3446            } else {
3447                fromexisting = true;
3448            }
3449
3450            if (boring == null || boring == UNKNOWN_BORING) {
3451                if (des < 0) {
3452                    des = (int) FloatMath.ceil(Layout.
3453                                    getDesiredWidth(mTransformed, mTextPaint));
3454                }
3455
3456                width = des;
3457            } else {
3458                width = boring.width;
3459            }
3460
3461            width = Math.max(width, mDrawableWidthTop);
3462            width = Math.max(width, mDrawableWidthBottom);
3463
3464            if (mHint != null) {
3465                int hintDes = -1;
3466                int hintWidth;
3467
3468                if (mHintLayout != null) {
3469                    hintDes = desired(mHintLayout);
3470                }
3471
3472                if (hintDes < 0) {
3473                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
3474                                                       mHintBoring);
3475                    if (hintBoring != null) {
3476                        mHintBoring = hintBoring;
3477                    }
3478                }
3479
3480                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
3481                    if (hintDes < 0) {
3482                        hintDes = (int) FloatMath.ceil(Layout.
3483                                        getDesiredWidth(mHint, mTextPaint));
3484                    }
3485
3486                    hintWidth = hintDes;
3487                } else {
3488                    hintWidth = hintBoring.width;
3489                }
3490
3491                if (hintWidth > width) {
3492                    width = hintWidth;
3493                }
3494            }
3495
3496            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
3497
3498            if (mMaxWidthMode == EMS) {
3499                width = Math.min(width, mMaxWidth * getLineHeight());
3500            } else {
3501                width = Math.min(width, mMaxWidth);
3502            }
3503
3504            if (mMinWidthMode == EMS) {
3505                width = Math.max(width, mMinWidth * getLineHeight());
3506            } else {
3507                width = Math.max(width, mMinWidth);
3508            }
3509
3510            // Check against our minimum width
3511            width = Math.max(width, getSuggestedMinimumWidth());
3512
3513            if (widthMode == MeasureSpec.AT_MOST) {
3514                width = Math.min(widthSize, width);
3515            }
3516        }
3517
3518        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
3519        int unpaddedWidth = want;
3520        int hintWant = want;
3521
3522        if (mHorizontallyScrolling)
3523            want = VERY_WIDE;
3524
3525        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
3526
3527        if (mLayout == null) {
3528            makeNewLayout(want, hintWant, boring, hintBoring,
3529                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
3530                          false);
3531        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
3532                   (mLayout.getEllipsizedWidth() !=
3533                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
3534            if (mHint == null && mEllipsize == null &&
3535                    want > mLayout.getWidth() &&
3536                    (mLayout instanceof BoringLayout ||
3537                        (fromexisting && des >= 0 && des <= want))) {
3538                mLayout.increaseWidthTo(want);
3539            } else {
3540                makeNewLayout(want, hintWant, boring, hintBoring,
3541                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
3542                              false);
3543            }
3544        } else {
3545            // Width has not changed.
3546        }
3547
3548        if (heightMode == MeasureSpec.EXACTLY) {
3549            // Parent has told us how big to be. So be it.
3550            height = heightSize;
3551            mDesiredHeightAtMeasure = -1;
3552        } else {
3553            int desired = getDesiredHeight();
3554
3555            height = desired;
3556            mDesiredHeightAtMeasure = desired;
3557
3558            if (heightMode == MeasureSpec.AT_MOST) {
3559                height = Math.min(desired, height);
3560            }
3561        }
3562
3563        int unpaddedHeight = height - getCompoundPaddingTop() -
3564                                getCompoundPaddingBottom();
3565        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
3566            unpaddedHeight = Math.min(unpaddedHeight,
3567                                      mLayout.getLineTop(mMaximum));
3568        }
3569
3570        /*
3571         * We didn't let makeNewLayout() register to bring the cursor into view,
3572         * so do it here if there is any possibility that it is needed.
3573         */
3574        if (mMovement != null ||
3575            mLayout.getWidth() > unpaddedWidth ||
3576            mLayout.getHeight() > unpaddedHeight) {
3577            registerForPreDraw();
3578        } else {
3579            scrollTo(0, 0);
3580        }
3581
3582        setMeasuredDimension(width, height);
3583    }
3584
3585    private int getDesiredHeight() {
3586        return Math.max(getDesiredHeight(mLayout, true),
3587                        getDesiredHeight(mHintLayout, false));
3588    }
3589
3590    private int getDesiredHeight(Layout layout, boolean cap) {
3591        if (layout == null) {
3592            return 0;
3593        }
3594
3595        int linecount = layout.getLineCount();
3596        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
3597        int desired = layout.getLineTop(linecount);
3598
3599        desired = Math.max(desired, mDrawableHeightLeft);
3600        desired = Math.max(desired, mDrawableHeightRight);
3601
3602        desired += pad;
3603
3604        if (mMaxMode == LINES) {
3605            /*
3606             * Don't cap the hint to a certain number of lines.
3607             * (Do cap it, though, if we have a maximum pixel height.)
3608             */
3609            if (cap) {
3610                if (linecount > mMaximum) {
3611                    desired = layout.getLineTop(mMaximum) +
3612                              layout.getBottomPadding();
3613
3614                    desired = Math.max(desired, mDrawableHeightLeft);
3615                    desired = Math.max(desired, mDrawableHeightRight);
3616
3617                    desired += pad;
3618                    linecount = mMaximum;
3619                }
3620            }
3621        } else {
3622            desired = Math.min(desired, mMaximum);
3623        }
3624
3625        if (mMinMode == LINES) {
3626            if (linecount < mMinimum) {
3627                desired += getLineHeight() * (mMinimum - linecount);
3628            }
3629        } else {
3630            desired = Math.max(desired, mMinimum);
3631        }
3632
3633        // Check against our minimum height
3634        desired = Math.max(desired, getSuggestedMinimumHeight());
3635
3636        return desired;
3637    }
3638
3639    /**
3640     * Check whether a change to the existing text layout requires a
3641     * new view layout.
3642     */
3643    private void checkForResize() {
3644        boolean sizeChanged = false;
3645
3646        if (mLayout != null) {
3647            // Check if our width changed
3648            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
3649                sizeChanged = true;
3650                invalidate();
3651            }
3652
3653            // Check if our height changed
3654            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
3655                int desiredHeight = getDesiredHeight();
3656
3657                if (desiredHeight != this.getHeight()) {
3658                    sizeChanged = true;
3659                }
3660            } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
3661                if (mDesiredHeightAtMeasure >= 0) {
3662                    int desiredHeight = getDesiredHeight();
3663
3664                    if (desiredHeight != mDesiredHeightAtMeasure) {
3665                        sizeChanged = true;
3666                    }
3667                }
3668            }
3669        }
3670
3671        if (sizeChanged) {
3672            requestLayout();
3673            // caller will have already invalidated
3674        }
3675    }
3676
3677    /**
3678     * Check whether entirely new text requires a new view layout
3679     * or merely a new text layout.
3680     */
3681    private void checkForRelayout() {
3682        // If we have a fixed width, we can just swap in a new text layout
3683        // if the text height stays the same or if the view height is fixed.
3684
3685        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
3686                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
3687                (mHint == null || mHintLayout != null) &&
3688                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
3689            // Static width, so try making a new text layout.
3690
3691            int oldht = mLayout.getHeight();
3692            int want = mLayout.getWidth();
3693            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3694
3695            /*
3696             * No need to bring the text into view, since the size is not
3697             * changing (unless we do the requestLayout(), in which case it
3698             * will happen at measure).
3699             */
3700            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3701                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
3702
3703            // In a fixed-height view, so use our new text layout.
3704            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
3705                mLayoutParams.height != LayoutParams.FILL_PARENT) {
3706                invalidate();
3707                return;
3708            }
3709
3710            // Dynamic height, but height has stayed the same,
3711            // so use our new text layout.
3712            if (mLayout.getHeight() == oldht &&
3713                (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
3714                invalidate();
3715                return;
3716            }
3717
3718            // We lose: the height has changed and we have a dynamic height.
3719            // Request a new view layout using our new text layout.
3720            requestLayout();
3721            invalidate();
3722        } else {
3723            // Dynamic width, so we have no choice but to request a new
3724            // view layout with a new text layout.
3725
3726            nullLayouts();
3727            requestLayout();
3728            invalidate();
3729        }
3730    }
3731
3732    /**
3733     * Returns true if anything changed.
3734     */
3735    private boolean bringTextIntoView() {
3736        int line = 0;
3737        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3738            line = mLayout.getLineCount() - 1;
3739        }
3740
3741        Layout.Alignment a = mLayout.getParagraphAlignment(line);
3742        int dir = mLayout.getParagraphDirection(line);
3743        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
3744        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
3745        int ht = mLayout.getHeight();
3746
3747        int scrollx, scrolly;
3748
3749        if (a == Layout.Alignment.ALIGN_CENTER) {
3750            /*
3751             * Keep centered if possible, or, if it is too wide to fit,
3752             * keep leading edge in view.
3753             */
3754
3755            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
3756            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
3757
3758            if (right - left < hspace) {
3759                scrollx = (right + left) / 2 - hspace / 2;
3760            } else {
3761                if (dir < 0) {
3762                    scrollx = right - hspace;
3763                } else {
3764                    scrollx = left;
3765                }
3766            }
3767        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
3768            /*
3769             * Keep leading edge in view.
3770             */
3771
3772            if (dir < 0) {
3773                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
3774                scrollx = right - hspace;
3775            } else {
3776                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
3777            }
3778        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
3779            /*
3780             * Keep trailing edge in view.
3781             */
3782
3783            if (dir < 0) {
3784                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
3785            } else {
3786                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
3787                scrollx = right - hspace;
3788            }
3789        }
3790
3791        if (ht < vspace) {
3792            scrolly = 0;
3793        } else {
3794            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3795                scrolly = ht - vspace;
3796            } else {
3797                scrolly = 0;
3798            }
3799        }
3800
3801        if (scrollx != mScrollX || scrolly != mScrollY) {
3802            scrollTo(scrollx, scrolly);
3803            return true;
3804        } else {
3805            return false;
3806        }
3807    }
3808
3809    /**
3810     * Returns true if anything changed.
3811     */
3812    private boolean bringPointIntoView(int offset) {
3813        boolean changed = false;
3814
3815        int line = mLayout.getLineForOffset(offset);
3816
3817        // FIXME: Is it okay to truncate this, or should we round?
3818        final int x = (int)mLayout.getPrimaryHorizontal(offset);
3819        final int top = mLayout.getLineTop(line);
3820        final int bottom = mLayout.getLineTop(line+1);
3821
3822        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
3823        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
3824        int ht = mLayout.getHeight();
3825
3826        int grav;
3827
3828        switch (mLayout.getParagraphAlignment(line)) {
3829            case ALIGN_NORMAL:
3830                grav = 1;
3831                break;
3832
3833            case ALIGN_OPPOSITE:
3834                grav = -1;
3835                break;
3836
3837            default:
3838                grav = 0;
3839        }
3840
3841        grav *= mLayout.getParagraphDirection(line);
3842
3843        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
3844        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
3845
3846        int hslack = (bottom - top) / 2;
3847        int vslack = hslack;
3848
3849        if (vslack > vspace / 4)
3850            vslack = vspace / 4;
3851        if (hslack > hspace / 4)
3852            hslack = hspace / 4;
3853
3854        int hs = mScrollX;
3855        int vs = mScrollY;
3856
3857        if (top - vs < vslack)
3858            vs = top - vslack;
3859        if (bottom - vs > vspace - vslack)
3860            vs = bottom - (vspace - vslack);
3861        if (ht - vs < vspace)
3862            vs = ht - vspace;
3863        if (0 - vs > 0)
3864            vs = 0;
3865
3866        if (grav != 0) {
3867            if (x - hs < hslack) {
3868                hs = x - hslack;
3869            }
3870            if (x - hs > hspace - hslack) {
3871                hs = x - (hspace - hslack);
3872            }
3873        }
3874
3875        if (grav < 0) {
3876            if (left - hs > 0)
3877                hs = left;
3878            if (right - hs < hspace)
3879                hs = right - hspace;
3880        } else if (grav > 0) {
3881            if (right - hs < hspace)
3882                hs = right - hspace;
3883            if (left - hs > 0)
3884                hs = left;
3885        } else /* grav == 0 */ {
3886            if (right - left <= hspace) {
3887                /*
3888                 * If the entire text fits, center it exactly.
3889                 */
3890                hs = left - (hspace - (right - left)) / 2;
3891            } else if (x > right - hslack) {
3892                /*
3893                 * If we are near the right edge, keep the right edge
3894                 * at the edge of the view.
3895                 */
3896                hs = right - hspace;
3897            } else if (x < left + hslack) {
3898                /*
3899                 * If we are near the left edge, keep the left edge
3900                 * at the edge of the view.
3901                 */
3902                hs = left;
3903            } else if (left > hs) {
3904                /*
3905                 * Is there whitespace visible at the left?  Fix it if so.
3906                 */
3907                hs = left;
3908            } else if (right < hs + hspace) {
3909                /*
3910                 * Is there whitespace visible at the right?  Fix it if so.
3911                 */
3912                hs = right - hspace;
3913            } else {
3914                /*
3915                 * Otherwise, float as needed.
3916                 */
3917                if (x - hs < hslack) {
3918                    hs = x - hslack;
3919                }
3920                if (x - hs > hspace - hslack) {
3921                    hs = x - (hspace - hslack);
3922                }
3923            }
3924        }
3925
3926        if (hs != mScrollX || vs != mScrollY) {
3927            if (mScroller == null) {
3928                scrollTo(hs, vs);
3929            } else {
3930                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
3931                int dx = hs - mScrollX;
3932                int dy = vs - mScrollY;
3933
3934                if (duration > ANIMATED_SCROLL_GAP) {
3935                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
3936                    invalidate();
3937                } else {
3938                    if (!mScroller.isFinished()) {
3939                        mScroller.abortAnimation();
3940                    }
3941
3942                    scrollBy(dx, dy);
3943                }
3944
3945                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
3946            }
3947
3948            changed = true;
3949        }
3950
3951        if (isFocused()) {
3952            // This offsets because getInterestingRect() is in terms of
3953            // viewport coordinates, but requestRectangleOnScreen()
3954            // is in terms of content coordinates.
3955
3956            Rect r = new Rect();
3957            getInterestingRect(r, x, top, bottom, line);
3958            r.offset(mScrollX, mScrollY);
3959
3960            if (requestRectangleOnScreen(r)) {
3961                changed = true;
3962            }
3963        }
3964
3965        return changed;
3966    }
3967
3968    @Override
3969    public void computeScroll() {
3970        if (mScroller != null) {
3971            if (mScroller.computeScrollOffset()) {
3972                mScrollX = mScroller.getCurrX();
3973                mScrollY = mScroller.getCurrY();
3974                postInvalidate();  // So we draw again
3975            }
3976        }
3977    }
3978
3979    private void getInterestingRect(Rect r, int h, int top, int bottom,
3980                                    int line) {
3981        top += getExtendedPaddingTop();
3982        bottom += getExtendedPaddingTop();
3983        h += getCompoundPaddingLeft();
3984
3985        if (line == 0)
3986            top -= getExtendedPaddingTop();
3987        if (line == mLayout.getLineCount() - 1)
3988            bottom += getExtendedPaddingBottom();
3989
3990        r.set(h, top, h, bottom);
3991        r.offset(-mScrollX, -mScrollY);
3992    }
3993
3994    @Override
3995    public void debug(int depth) {
3996        super.debug(depth);
3997
3998        String output = debugIndent(depth);
3999        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
4000                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
4001                + "} ";
4002
4003        if (mText != null) {
4004
4005            output += "mText=\"" + mText + "\" ";
4006            if (mLayout != null) {
4007                output += "mLayout width=" + mLayout.getWidth()
4008                        + " height=" + mLayout.getHeight();
4009            }
4010        } else {
4011            output += "mText=NULL";
4012        }
4013        Log.d(VIEW_LOG_TAG, output);
4014    }
4015
4016    /**
4017     * Convenience for {@link Selection#getSelectionStart}.
4018     */
4019    public int getSelectionStart() {
4020        return Selection.getSelectionStart(getText());
4021    }
4022
4023    /**
4024     * Convenience for {@link Selection#getSelectionEnd}.
4025     */
4026    public int getSelectionEnd() {
4027        return Selection.getSelectionEnd(getText());
4028    }
4029
4030    /**
4031     * Return true iff there is a selection inside this text view.
4032     */
4033    public boolean hasSelection() {
4034        return getSelectionStart() != getSelectionEnd();
4035    }
4036
4037    /**
4038     * Sets the properties of this field (lines, horizontally scrolling,
4039     * transformation method) to be for a single-line input.
4040     *
4041     * @attr ref android.R.styleable#TextView_singleLine
4042     */
4043    public void setSingleLine() {
4044        setSingleLine(true);
4045    }
4046
4047    /**
4048     * If true, sets the properties of this field (lines, horizontally
4049     * scrolling, transformation method) to be for a single-line input;
4050     * if false, restores these to the default conditions.
4051     * Note that calling this with false restores default conditions,
4052     * not necessarily those that were in effect prior to calling
4053     * it with true.
4054     *
4055     * @attr ref android.R.styleable#TextView_singleLine
4056     */
4057    public void setSingleLine(boolean singleLine) {
4058        mSingleLine = singleLine;
4059
4060        if (singleLine) {
4061            setLines(1);
4062            setHorizontallyScrolling(true);
4063            setTransformationMethod(SingleLineTransformationMethod.
4064                                    getInstance());
4065        } else {
4066            setMaxLines(Integer.MAX_VALUE);
4067            setHorizontallyScrolling(false);
4068            setTransformationMethod(null);
4069        }
4070    }
4071
4072    /**
4073     * Causes words in the text that are longer than the view is wide
4074     * to be ellipsized instead of broken in the middle.  You may also
4075     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
4076     * to constrain the text toa single line.  Use <code>null</code>
4077     * to turn off ellipsizing.
4078     *
4079     * @attr ref android.R.styleable#TextView_ellipsize
4080     */
4081    public void setEllipsize(TextUtils.TruncateAt where) {
4082        mEllipsize = where;
4083
4084        if (mLayout != null) {
4085            nullLayouts();
4086            requestLayout();
4087            invalidate();
4088        }
4089    }
4090
4091    /**
4092     * Returns where, if anywhere, words that are longer than the view
4093     * is wide should be ellipsized.
4094     */
4095    public TextUtils.TruncateAt getEllipsize() {
4096        return mEllipsize;
4097    }
4098
4099    /**
4100     * Set the TextView so that when it takes focus, all the text is
4101     * selected.
4102     *
4103     * @attr ref android.R.styleable#TextView_selectAllOnFocus
4104     */
4105    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
4106        mSelectAllOnFocus = selectAllOnFocus;
4107
4108        if (selectAllOnFocus && !(mText instanceof Spannable)) {
4109            setText(mText, BufferType.SPANNABLE);
4110        }
4111    }
4112
4113    /**
4114     * Set whether the cursor is visible.  The default is true.
4115     *
4116     * @attr ref android.R.styleable#TextView_cursorVisible
4117     */
4118    public void setCursorVisible(boolean visible) {
4119        mCursorVisible = visible;
4120        invalidate();
4121
4122        if (visible) {
4123            makeBlink();
4124        } else if (mBlink != null) {
4125            mBlink.removeCallbacks(mBlink);
4126        }
4127    }
4128
4129    /**
4130     * This method is called when the text is changed, in case any
4131     * subclasses would like to know.
4132     *
4133     * @param text The text the TextView is displaying.
4134     * @param start The offset of the start of the range of the text
4135     *              that was modified.
4136     * @param before The offset of the former end of the range of the
4137     *               text that was modified.  If text was simply inserted,
4138     *               this will be the same as <code>start</code>.
4139     *               If text was replaced with new text or deleted, the
4140     *               length of the old text was <code>before-start</code>.
4141     * @param after The offset of the end of the range of the text
4142     *              that was modified.  If text was simply deleted,
4143     *              this will be the same as <code>start</code>.
4144     *              If text was replaced with new text or inserted,
4145     *              the length of the new text is <code>after-start</code>.
4146     */
4147    protected void onTextChanged(CharSequence text,
4148                                 int start, int before, int after) {
4149    }
4150
4151    /**
4152     * Adds a TextWatcher to the list of those whose methods are called
4153     * whenever this TextView's text changes.
4154     */
4155    public void addTextChangedListener(TextWatcher watcher) {
4156        if (mListeners == null) {
4157            mListeners = new ArrayList<TextWatcher>();
4158        }
4159
4160        mListeners.add(watcher);
4161    }
4162
4163    /**
4164     * Removes the specified TextWatcher from the list of those whose
4165     * methods are called
4166     * whenever this TextView's text changes.
4167     */
4168    public void removeTextChangedListener(TextWatcher watcher) {
4169        if (mListeners != null) {
4170            int i = mListeners.indexOf(watcher);
4171
4172            if (i >= 0) {
4173                mListeners.remove(i);
4174            }
4175        }
4176    }
4177
4178    private void sendBeforeTextChanged(CharSequence text, int start, int before,
4179                                   int after) {
4180        if (mListeners != null) {
4181            final ArrayList<TextWatcher> list = mListeners;
4182            final int count = list.size();
4183            for (int i = 0; i < count; i++) {
4184                list.get(i).beforeTextChanged(text, start, before, after);
4185            }
4186        }
4187    }
4188
4189    private void sendOnTextChanged(CharSequence text, int start, int before,
4190                                   int after) {
4191        if (mListeners != null) {
4192            final ArrayList<TextWatcher> list = mListeners;
4193            final int count = list.size();
4194            for (int i = 0; i < count; i++) {
4195                list.get(i).onTextChanged(text, start, before, after);
4196            }
4197        }
4198    }
4199
4200    private void sendAfterTextChanged(Editable text) {
4201        if (mListeners != null) {
4202            final ArrayList<TextWatcher> list = mListeners;
4203            final int count = list.size();
4204            for (int i = 0; i < count; i++) {
4205                list.get(i).afterTextChanged(text);
4206            }
4207        }
4208    }
4209
4210    private class ChangeWatcher
4211    extends Handler
4212    implements TextWatcher, SpanWatcher {
4213        public void beforeTextChanged(CharSequence buffer, int start,
4214                                      int before, int after) {
4215            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
4216        }
4217
4218        public void onTextChanged(CharSequence buffer, int start,
4219                                  int before, int after) {
4220            invalidate();
4221
4222            int curs = Selection.getSelectionStart(buffer);
4223
4224            if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4225                                 Gravity.BOTTOM) {
4226                registerForPreDraw();
4227            }
4228
4229            if (curs >= 0) {
4230                mHighlightPathBogus = true;
4231
4232                if (isFocused()) {
4233                    mShowCursor = SystemClock.uptimeMillis();
4234                    makeBlink();
4235                }
4236            }
4237
4238            checkForResize();
4239
4240            TextView.this.sendOnTextChanged(buffer, start, before, after);
4241            TextView.this.onTextChanged(buffer, start, before, after);
4242        }
4243
4244        public void afterTextChanged(Editable buffer) {
4245            TextView.this.sendAfterTextChanged(buffer);
4246        }
4247
4248        private void spanChange(Spanned buf, Object what, int o, int n) {
4249            // XXX Make the start and end move together if this ends up
4250            // spending too much time invalidating.
4251
4252            if (what == Selection.SELECTION_END) {
4253                mHighlightPathBogus = true;
4254
4255                if (!isFocused()) {
4256                    mSelectionMoved = true;
4257                }
4258
4259                if (o >= 0 || n >= 0) {
4260                    invalidateCursor(Selection.getSelectionStart(buf), o, n);
4261                    registerForPreDraw();
4262
4263                    if (isFocused()) {
4264                        mShowCursor = SystemClock.uptimeMillis();
4265                        makeBlink();
4266                    }
4267                }
4268            }
4269
4270            if (what == Selection.SELECTION_START) {
4271                mHighlightPathBogus = true;
4272
4273                if (!isFocused()) {
4274                    mSelectionMoved = true;
4275                }
4276
4277                if (o >= 0 || n >= 0) {
4278                    invalidateCursor(Selection.getSelectionEnd(buf), o, n);
4279                }
4280            }
4281
4282            if (what instanceof UpdateLayout ||
4283                what instanceof ParagraphStyle) {
4284                invalidate();
4285                mHighlightPathBogus = true;
4286                checkForResize();
4287            }
4288
4289            if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
4290                mHighlightPathBogus = true;
4291
4292                if (Selection.getSelectionStart(buf) >= 0) {
4293                    invalidateCursor();
4294                }
4295            }
4296        }
4297
4298        public void onSpanChanged(Spannable buf,
4299                                  Object what, int s, int e, int st, int en) {
4300            spanChange(buf, what, s, st);
4301        }
4302
4303        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
4304            spanChange(buf, what, -1, s);
4305        }
4306
4307        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
4308            spanChange(buf, what, s, -1);
4309        }
4310    }
4311
4312    private void makeBlink() {
4313        if (!mCursorVisible) {
4314            if (mBlink != null) {
4315                mBlink.removeCallbacks(mBlink);
4316            }
4317
4318            return;
4319        }
4320
4321        if (mBlink == null)
4322            mBlink = new Blink(this);
4323
4324        mBlink.removeCallbacks(mBlink);
4325        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
4326    }
4327
4328    @Override
4329    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
4330        mShowCursor = SystemClock.uptimeMillis();
4331
4332        if (focused) {
4333            int selStart = getSelectionStart();
4334            int selEnd = getSelectionEnd();
4335
4336            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
4337                boolean selMoved = mSelectionMoved;
4338
4339                if (mMovement != null) {
4340                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
4341                }
4342
4343                if (mSelectAllOnFocus) {
4344                    Selection.setSelection((Spannable) mText, 0, mText.length());
4345                }
4346
4347                if (selMoved && selStart >= 0 && selEnd >= 0) {
4348                    /*
4349                     * Someone intentionally set the selection, so let them
4350                     * do whatever it is that they wanted to do instead of
4351                     * the default on-focus behavior.  We reset the selection
4352                     * here instead of just skipping the onTakeFocus() call
4353                     * because some movement methods do something other than
4354                     * just setting the selection in theirs and we still
4355                     * need to go through that path.
4356                     */
4357
4358                    Selection.setSelection((Spannable) mText, selStart, selEnd);
4359                }
4360            }
4361
4362            mFrozenWithFocus = false;
4363            mSelectionMoved = false;
4364
4365            if (mText instanceof Spannable) {
4366                Spannable sp = (Spannable) mText;
4367                MetaKeyKeyListener.resetMetaState(sp);
4368            }
4369
4370            makeBlink();
4371
4372            if (mError != null) {
4373                showError();
4374            }
4375        } else {
4376            if (mError != null) {
4377                hideError();
4378            }
4379        }
4380
4381        if (mTransformation != null) {
4382            mTransformation.onFocusChanged(this, mText, focused, direction,
4383                                           previouslyFocusedRect);
4384        }
4385
4386        super.onFocusChanged(focused, direction, previouslyFocusedRect);
4387    }
4388
4389    public void onWindowFocusChanged(boolean hasWindowFocus) {
4390        super.onWindowFocusChanged(hasWindowFocus);
4391
4392        if (hasWindowFocus) {
4393            if (mBlink != null) {
4394                mBlink.uncancel();
4395
4396                if (isFocused()) {
4397                    mShowCursor = SystemClock.uptimeMillis();
4398                    makeBlink();
4399                }
4400            }
4401        } else {
4402            if (mBlink != null) {
4403                mBlink.cancel();
4404            }
4405        }
4406    }
4407
4408    @Override
4409    public boolean onTouchEvent(MotionEvent event) {
4410        final boolean superResult = super.onTouchEvent(event);
4411
4412        /*
4413         * Don't handle the release after a long press, because it will
4414         * move the selection away from whatever the menu action was
4415         * trying to affect.
4416         */
4417        if (mEatTouchRelease && event.getAction() == MotionEvent.ACTION_UP) {
4418            mEatTouchRelease = false;
4419            return superResult;
4420        }
4421
4422        if (mMovement != null && mText instanceof Spannable &&
4423            mLayout != null) {
4424            if (mMovement.onTouchEvent(this, (Spannable) mText, event)) {
4425                return true;
4426            }
4427        }
4428
4429        return superResult;
4430    }
4431
4432    @Override
4433    public boolean onTrackballEvent(MotionEvent event) {
4434        if (mMovement != null && mText instanceof Spannable &&
4435            mLayout != null) {
4436            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
4437                return true;
4438            }
4439        }
4440
4441        return super.onTrackballEvent(event);
4442    }
4443
4444    public void setScroller(Scroller s) {
4445        mScroller = s;
4446    }
4447
4448    private static class Blink extends Handler
4449            implements Runnable {
4450        private WeakReference<TextView> mView;
4451        private boolean mCancelled;
4452
4453        public Blink(TextView v) {
4454            mView = new WeakReference<TextView>(v);
4455        }
4456
4457        public void run() {
4458            if (mCancelled) {
4459                return;
4460            }
4461
4462            removeCallbacks(Blink.this);
4463
4464            TextView tv = mView.get();
4465
4466            if (tv != null && tv.isFocused()) {
4467                int st = Selection.getSelectionStart(tv.mText);
4468                int en = Selection.getSelectionEnd(tv.mText);
4469
4470                if (st == en && st >= 0 && en >= 0) {
4471                    if (tv.mLayout != null) {
4472                        tv.invalidateCursorPath();
4473                    }
4474
4475                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
4476                }
4477            }
4478        }
4479
4480        void cancel() {
4481            if (!mCancelled) {
4482                removeCallbacks(Blink.this);
4483                mCancelled = true;
4484            }
4485        }
4486
4487        void uncancel() {
4488            mCancelled = false;
4489        }
4490    }
4491
4492    @Override
4493    protected int computeHorizontalScrollRange() {
4494        if (mLayout != null)
4495            return mLayout.getWidth();
4496
4497        return super.computeHorizontalScrollRange();
4498    }
4499
4500    @Override
4501    protected int computeVerticalScrollRange() {
4502        if (mLayout != null)
4503            return mLayout.getHeight();
4504
4505        return super.computeVerticalScrollRange();
4506    }
4507
4508    public enum BufferType {
4509        NORMAL, SPANNABLE, EDITABLE,
4510    }
4511
4512    /**
4513     * Returns the TextView_textColor attribute from the
4514     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
4515     * from the TextView_textAppearance attribute, if TextView_textColor
4516     * was not set directly.
4517     */
4518    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
4519        ColorStateList colors;
4520        colors = attrs.getColorStateList(com.android.internal.R.styleable.
4521                                         TextView_textColor);
4522
4523        if (colors == null) {
4524            int ap = attrs.getResourceId(com.android.internal.R.styleable.
4525                                         TextView_textAppearance, -1);
4526            if (ap != -1) {
4527                TypedArray appearance;
4528                appearance = context.obtainStyledAttributes(ap,
4529                                            com.android.internal.R.styleable.TextAppearance);
4530                colors = appearance.getColorStateList(com.android.internal.R.styleable.
4531                                                  TextAppearance_textColor);
4532                appearance.recycle();
4533            }
4534        }
4535
4536        return colors;
4537    }
4538
4539    /**
4540     * Returns the default color from the TextView_textColor attribute
4541     * from the AttributeSet, if set, or the default color from the
4542     * TextAppearance_textColor from the TextView_textAppearance attribute,
4543     * if TextView_textColor was not set directly.
4544     */
4545    public static int getTextColor(Context context,
4546                                   TypedArray attrs,
4547                                   int def) {
4548        ColorStateList colors = getTextColors(context, attrs);
4549
4550        if (colors == null) {
4551            return def;
4552        } else {
4553            return colors.getDefaultColor();
4554        }
4555    }
4556
4557    @Override
4558    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
4559        switch (keyCode) {
4560        case KeyEvent.KEYCODE_A:
4561            if (canSelectAll()) {
4562                return onMenu(ID_SELECT_ALL);
4563            }
4564
4565            break;
4566
4567        case KeyEvent.KEYCODE_X:
4568            if (canCut()) {
4569                return onMenu(ID_CUT);
4570            }
4571
4572            break;
4573
4574        case KeyEvent.KEYCODE_C:
4575            if (canCopy()) {
4576                return onMenu(ID_COPY);
4577            }
4578
4579            break;
4580
4581        case KeyEvent.KEYCODE_V:
4582            if (canPaste()) {
4583                return onMenu(ID_PASTE);
4584            }
4585
4586            break;
4587        }
4588
4589        return super.onKeyShortcut(keyCode, event);
4590    }
4591
4592    private boolean canSelectAll() {
4593        if (mText instanceof Spannable && mText.length() != 0 &&
4594            mMovement != null && mMovement.canSelectArbitrarily()) {
4595            return true;
4596        }
4597
4598        return false;
4599    }
4600
4601    private boolean canCut() {
4602        if (mText.length() > 0 && getSelectionStart() >= 0) {
4603            if (mText instanceof Editable && mInput != null) {
4604                return true;
4605            }
4606        }
4607
4608        return false;
4609    }
4610
4611    private boolean canCopy() {
4612        if (mText.length() > 0 && getSelectionStart() >= 0) {
4613            return true;
4614        }
4615
4616        return false;
4617    }
4618
4619    private boolean canPaste() {
4620        if (mText instanceof Editable && mInput != null &&
4621            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
4622            ClipboardManager clip = (ClipboardManager)getContext()
4623                    .getSystemService(Context.CLIPBOARD_SERVICE);
4624            if (clip.hasText()) {
4625                return true;
4626            }
4627        }
4628
4629        return false;
4630    }
4631
4632    @Override
4633    protected void onCreateContextMenu(ContextMenu menu) {
4634        super.onCreateContextMenu(menu);
4635
4636        if (!isFocused()) {
4637            return;
4638        }
4639
4640        MenuHandler handler = new MenuHandler();
4641
4642        if (canSelectAll()) {
4643            menu.add(0, ID_SELECT_ALL, 0,
4644                    com.android.internal.R.string.selectAll).
4645                setOnMenuItemClickListener(handler).
4646                setAlphabeticShortcut('a');
4647        }
4648
4649        boolean selection = getSelectionStart() != getSelectionEnd();
4650
4651        if (canCut()) {
4652            int name;
4653            if (selection) {
4654                name = com.android.internal.R.string.cut;
4655            } else {
4656                name = com.android.internal.R.string.cutAll;
4657            }
4658
4659            menu.add(0, ID_CUT, 0, name).
4660                setOnMenuItemClickListener(handler).
4661                setAlphabeticShortcut('x');
4662        }
4663
4664        if (canCopy()) {
4665            int name;
4666            if (selection) {
4667                name = com.android.internal.R.string.copy;
4668            } else {
4669                name = com.android.internal.R.string.copyAll;
4670            }
4671
4672            menu.add(0, ID_COPY, 0, name).
4673                setOnMenuItemClickListener(handler).
4674                setAlphabeticShortcut('c');
4675        }
4676
4677        if (canPaste()) {
4678            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
4679                    setOnMenuItemClickListener(handler).
4680                    setAlphabeticShortcut('v');
4681        }
4682
4683        if (mText instanceof Spanned) {
4684            int selStart = getSelectionStart();
4685            int selEnd = getSelectionEnd();
4686
4687            int min = Math.min(selStart, selEnd);
4688            int max = Math.max(selStart, selEnd);
4689
4690            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
4691                                                        URLSpan.class);
4692            if (urls.length == 1) {
4693                menu.add(0, ID_COPY_URL, 0,
4694                         com.android.internal.R.string.copyUrl).
4695                            setOnMenuItemClickListener(handler);
4696            }
4697        }
4698    }
4699
4700    private static final int ID_SELECT_ALL = 101;
4701    private static final int ID_CUT = 102;
4702    private static final int ID_COPY = 103;
4703    private static final int ID_PASTE = 104;
4704    private static final int ID_COPY_URL = 105;
4705
4706    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
4707        public boolean onMenuItemClick(MenuItem item) {
4708            return onMenu(item.getItemId());
4709        }
4710    }
4711
4712    private boolean onMenu(int id) {
4713        int selStart = getSelectionStart();
4714        int selEnd = getSelectionEnd();
4715
4716        int min = Math.min(selStart, selEnd);
4717        int max = Math.max(selStart, selEnd);
4718
4719        if (min < 0) {
4720            min = 0;
4721        }
4722        if (max < 0) {
4723            max = 0;
4724        }
4725
4726        ClipboardManager clip = (ClipboardManager)getContext()
4727                .getSystemService(Context.CLIPBOARD_SERVICE);
4728
4729        switch (id) {
4730            case ID_SELECT_ALL:
4731                Selection.setSelection((Spannable) mText, 0,
4732                                        mText.length());
4733                return true;
4734
4735            case ID_CUT:
4736                if (min == max) {
4737                    min = 0;
4738                    max = mText.length();
4739                }
4740
4741                clip.setText(mTransformed.subSequence(min, max));
4742                ((Editable) mText).delete(min, max);
4743                return true;
4744
4745            case ID_COPY:
4746                if (min == max) {
4747                    min = 0;
4748                    max = mText.length();
4749                }
4750
4751                clip.setText(mTransformed.subSequence(min, max));
4752                return true;
4753
4754            case ID_PASTE:
4755                CharSequence paste = clip.getText();
4756
4757                if (paste != null) {
4758                    Selection.setSelection((Spannable) mText, max);
4759                    ((Editable) mText).replace(min, max, paste);
4760                }
4761
4762                return true;
4763
4764            case ID_COPY_URL:
4765                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
4766                                                       URLSpan.class);
4767                if (urls.length == 1) {
4768                    clip.setText(urls[0].getURL());
4769                }
4770
4771                return true;
4772        }
4773
4774        return false;
4775    }
4776
4777    public boolean performLongClick() {
4778        if (super.performLongClick()) {
4779            mEatTouchRelease = true;
4780            return true;
4781        }
4782
4783        return false;
4784    }
4785
4786    private boolean mEatTouchRelease = false;
4787
4788    @ViewDebug.ExportedProperty
4789    private CharSequence            mText;
4790    private CharSequence            mTransformed;
4791    private BufferType              mBufferType = BufferType.NORMAL;
4792
4793    private CharSequence            mHint;
4794    private Layout                  mHintLayout;
4795
4796    private KeyListener             mInput;
4797    private MovementMethod          mMovement;
4798    private TransformationMethod    mTransformation;
4799    private ChangeWatcher           mChangeWatcher;
4800
4801    private ArrayList<TextWatcher>  mListeners = null;
4802
4803    // display attributes
4804    private TextPaint mTextPaint;
4805    private Paint                   mHighlightPaint;
4806    private int                     mHighlightColor = 0xFFBBDDFF;
4807    private Layout                  mLayout;
4808
4809    private long                    mShowCursor;
4810    private Blink                   mBlink;
4811    private boolean                 mCursorVisible = true;
4812
4813    private boolean                 mSelectAllOnFocus = false;
4814
4815    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
4816    private boolean                 mHorizontallyScrolling;
4817
4818    private int                     mAutoLinkMask;
4819    private boolean                 mLinksClickable = true;
4820
4821    private float                   mSpacingMult = 1;
4822    private float                   mSpacingAdd = 0;
4823
4824    private static final int        LINES = 1;
4825    private static final int        EMS = LINES;
4826    private static final int        PIXELS = 2;
4827
4828    private int                     mMaximum = Integer.MAX_VALUE;
4829    private int                     mMaxMode = LINES;
4830    private int                     mMinimum = 0;
4831    private int                     mMinMode = LINES;
4832
4833    private int                     mMaxWidth = Integer.MAX_VALUE;
4834    private int                     mMaxWidthMode = PIXELS;
4835    private int                     mMinWidth = 0;
4836    private int                     mMinWidthMode = PIXELS;
4837
4838    private boolean                 mSingleLine;
4839    private int                     mDesiredHeightAtMeasure = -1;
4840    private boolean                 mIncludePad = true;
4841
4842    // tmp primitives, so we don't alloc them on each draw
4843    private Path                    mHighlightPath;
4844    private boolean                 mHighlightPathBogus = true;
4845    private static RectF            sTempRect = new RectF();
4846
4847    // XXX should be much larger
4848    private static final int        VERY_WIDE = 16384;
4849
4850    private static final int        BLINK = 500;
4851
4852    private static final int ANIMATED_SCROLL_GAP = 250;
4853    private long mLastScroll;
4854    private Scroller mScroller = null;
4855
4856    private BoringLayout.Metrics mBoring;
4857    private BoringLayout.Metrics mHintBoring;
4858
4859    private BoringLayout mSavedLayout, mSavedHintLayout;
4860
4861
4862
4863    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
4864    private InputFilter[] mFilters = NO_FILTERS;
4865    private static final Spanned EMPTY_SPANNED = new SpannedString("");
4866}
4867