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