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