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