TextView.java revision 843ef36f7b96cc19ea7d2996b7c8661b41ec3452
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            mInputType = 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            mUserSetTextScaleX = true;
1645            mTextPaint.setTextScaleX(size);
1646
1647            if (mLayout != null) {
1648                nullLayouts();
1649                requestLayout();
1650                invalidate();
1651            }
1652        }
1653    }
1654
1655    /**
1656     * Sets the typeface and style in which the text should be displayed.
1657     * Note that not all Typeface families actually have bold and italic
1658     * variants, so you may need to use
1659     * {@link #setTypeface(Typeface, int)} to get the appearance
1660     * that you actually want.
1661     *
1662     * @attr ref android.R.styleable#TextView_typeface
1663     * @attr ref android.R.styleable#TextView_textStyle
1664     */
1665    public void setTypeface(Typeface tf) {
1666        if (mTextPaint.getTypeface() != tf) {
1667            mTextPaint.setTypeface(tf);
1668
1669            if (mLayout != null) {
1670                nullLayouts();
1671                requestLayout();
1672                invalidate();
1673            }
1674        }
1675    }
1676
1677    /**
1678     * @return the current typeface and style in which the text is being
1679     * displayed.
1680     */
1681    public Typeface getTypeface() {
1682        return mTextPaint.getTypeface();
1683    }
1684
1685    /**
1686     * Sets the text color for all the states (normal, selected,
1687     * focused) to be this color.
1688     *
1689     * @attr ref android.R.styleable#TextView_textColor
1690     */
1691    @android.view.RemotableViewMethod
1692    public void setTextColor(int color) {
1693        mTextColor = ColorStateList.valueOf(color);
1694        updateTextColors();
1695    }
1696
1697    /**
1698     * Sets the text color.
1699     *
1700     * @attr ref android.R.styleable#TextView_textColor
1701     */
1702    public void setTextColor(ColorStateList colors) {
1703        if (colors == null) {
1704            throw new NullPointerException();
1705        }
1706
1707        mTextColor = colors;
1708        updateTextColors();
1709    }
1710
1711    /**
1712     * Return the set of text colors.
1713     *
1714     * @return Returns the set of text colors.
1715     */
1716    public final ColorStateList getTextColors() {
1717        return mTextColor;
1718    }
1719
1720    /**
1721     * <p>Return the current color selected for normal text.</p>
1722     *
1723     * @return Returns the current text color.
1724     */
1725    public final int getCurrentTextColor() {
1726        return mCurTextColor;
1727    }
1728
1729    /**
1730     * Sets the color used to display the selection highlight.
1731     *
1732     * @attr ref android.R.styleable#TextView_textColorHighlight
1733     */
1734    @android.view.RemotableViewMethod
1735    public void setHighlightColor(int color) {
1736        if (mHighlightColor != color) {
1737            mHighlightColor = color;
1738            invalidate();
1739        }
1740    }
1741
1742    /**
1743     * Gives the text a shadow of the specified radius and color, the specified
1744     * distance from its normal position.
1745     *
1746     * @attr ref android.R.styleable#TextView_shadowColor
1747     * @attr ref android.R.styleable#TextView_shadowDx
1748     * @attr ref android.R.styleable#TextView_shadowDy
1749     * @attr ref android.R.styleable#TextView_shadowRadius
1750     */
1751    public void setShadowLayer(float radius, float dx, float dy, int color) {
1752        mTextPaint.setShadowLayer(radius, dx, dy, color);
1753
1754        mShadowRadius = radius;
1755        mShadowDx = dx;
1756        mShadowDy = dy;
1757
1758        invalidate();
1759    }
1760
1761    /**
1762     * @return the base paint used for the text.  Please use this only to
1763     * consult the Paint's properties and not to change them.
1764     */
1765    public TextPaint getPaint() {
1766        return mTextPaint;
1767    }
1768
1769    /**
1770     * Sets the autolink mask of the text.  See {@link
1771     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1772     * possible values.
1773     *
1774     * @attr ref android.R.styleable#TextView_autoLink
1775     */
1776    @android.view.RemotableViewMethod
1777    public final void setAutoLinkMask(int mask) {
1778        mAutoLinkMask = mask;
1779    }
1780
1781    /**
1782     * Sets whether the movement method will automatically be set to
1783     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1784     * set to nonzero and links are detected in {@link #setText}.
1785     * The default is true.
1786     *
1787     * @attr ref android.R.styleable#TextView_linksClickable
1788     */
1789    @android.view.RemotableViewMethod
1790    public final void setLinksClickable(boolean whether) {
1791        mLinksClickable = whether;
1792    }
1793
1794    /**
1795     * Returns whether the movement method will automatically be set to
1796     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1797     * set to nonzero and links are detected in {@link #setText}.
1798     * The default is true.
1799     *
1800     * @attr ref android.R.styleable#TextView_linksClickable
1801     */
1802    public final boolean getLinksClickable() {
1803        return mLinksClickable;
1804    }
1805
1806    /**
1807     * Returns the list of URLSpans attached to the text
1808     * (by {@link Linkify} or otherwise) if any.  You can call
1809     * {@link URLSpan#getURL} on them to find where they link to
1810     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
1811     * to find the region of the text they are attached to.
1812     */
1813    public URLSpan[] getUrls() {
1814        if (mText instanceof Spanned) {
1815            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
1816        } else {
1817            return new URLSpan[0];
1818        }
1819    }
1820
1821    /**
1822     * Sets the color of the hint text.
1823     *
1824     * @attr ref android.R.styleable#TextView_textColorHint
1825     */
1826    @android.view.RemotableViewMethod
1827    public final void setHintTextColor(int color) {
1828        mHintTextColor = ColorStateList.valueOf(color);
1829        updateTextColors();
1830    }
1831
1832    /**
1833     * Sets the color of the hint text.
1834     *
1835     * @attr ref android.R.styleable#TextView_textColorHint
1836     */
1837    public final void setHintTextColor(ColorStateList colors) {
1838        mHintTextColor = colors;
1839        updateTextColors();
1840    }
1841
1842    /**
1843     * <p>Return the color used to paint the hint text.</p>
1844     *
1845     * @return Returns the list of hint text colors.
1846     */
1847    public final ColorStateList getHintTextColors() {
1848        return mHintTextColor;
1849    }
1850
1851    /**
1852     * <p>Return the current color selected to paint the hint text.</p>
1853     *
1854     * @return Returns the current hint text color.
1855     */
1856    public final int getCurrentHintTextColor() {
1857        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
1858    }
1859
1860    /**
1861     * Sets the color of links in the text.
1862     *
1863     * @attr ref android.R.styleable#TextView_textColorLink
1864     */
1865    @android.view.RemotableViewMethod
1866    public final void setLinkTextColor(int color) {
1867        mLinkTextColor = ColorStateList.valueOf(color);
1868        updateTextColors();
1869    }
1870
1871    /**
1872     * Sets the color of links in the text.
1873     *
1874     * @attr ref android.R.styleable#TextView_textColorLink
1875     */
1876    public final void setLinkTextColor(ColorStateList colors) {
1877        mLinkTextColor = colors;
1878        updateTextColors();
1879    }
1880
1881    /**
1882     * <p>Returns the color used to paint links in the text.</p>
1883     *
1884     * @return Returns the list of link text colors.
1885     */
1886    public final ColorStateList getLinkTextColors() {
1887        return mLinkTextColor;
1888    }
1889
1890    /**
1891     * Sets the horizontal alignment of the text and the
1892     * vertical gravity that will be used when there is extra space
1893     * in the TextView beyond what is required for the text itself.
1894     *
1895     * @see android.view.Gravity
1896     * @attr ref android.R.styleable#TextView_gravity
1897     */
1898    public void setGravity(int gravity) {
1899        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
1900            gravity |= Gravity.LEFT;
1901        }
1902        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
1903            gravity |= Gravity.TOP;
1904        }
1905
1906        boolean newLayout = false;
1907
1908        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
1909            (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
1910            newLayout = true;
1911        }
1912
1913        if (gravity != mGravity) {
1914            invalidate();
1915        }
1916
1917        mGravity = gravity;
1918
1919        if (mLayout != null && newLayout) {
1920            // XXX this is heavy-handed because no actual content changes.
1921            int want = mLayout.getWidth();
1922            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
1923
1924            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
1925                          mRight - mLeft - getCompoundPaddingLeft() -
1926                          getCompoundPaddingRight(), true);
1927        }
1928    }
1929
1930    /**
1931     * Returns the horizontal and vertical alignment of this TextView.
1932     *
1933     * @see android.view.Gravity
1934     * @attr ref android.R.styleable#TextView_gravity
1935     */
1936    public int getGravity() {
1937        return mGravity;
1938    }
1939
1940    /**
1941     * @return the flags on the Paint being used to display the text.
1942     * @see Paint#getFlags
1943     */
1944    public int getPaintFlags() {
1945        return mTextPaint.getFlags();
1946    }
1947
1948    /**
1949     * Sets flags on the Paint being used to display the text and
1950     * reflows the text if they are different from the old flags.
1951     * @see Paint#setFlags
1952     */
1953    @android.view.RemotableViewMethod
1954    public void setPaintFlags(int flags) {
1955        if (mTextPaint.getFlags() != flags) {
1956            mTextPaint.setFlags(flags);
1957
1958            if (mLayout != null) {
1959                nullLayouts();
1960                requestLayout();
1961                invalidate();
1962            }
1963        }
1964    }
1965
1966    /**
1967     * Sets whether the text should be allowed to be wider than the
1968     * View is.  If false, it will be wrapped to the width of the View.
1969     *
1970     * @attr ref android.R.styleable#TextView_scrollHorizontally
1971     */
1972    public void setHorizontallyScrolling(boolean whether) {
1973        mHorizontallyScrolling = whether;
1974
1975        if (mLayout != null) {
1976            nullLayouts();
1977            requestLayout();
1978            invalidate();
1979        }
1980    }
1981
1982    /**
1983     * Makes the TextView at least this many lines tall
1984     *
1985     * @attr ref android.R.styleable#TextView_minLines
1986     */
1987    @android.view.RemotableViewMethod
1988    public void setMinLines(int minlines) {
1989        mMinimum = minlines;
1990        mMinMode = LINES;
1991
1992        requestLayout();
1993        invalidate();
1994    }
1995
1996    /**
1997     * Makes the TextView at least this many pixels tall
1998     *
1999     * @attr ref android.R.styleable#TextView_minHeight
2000     */
2001    @android.view.RemotableViewMethod
2002    public void setMinHeight(int minHeight) {
2003        mMinimum = minHeight;
2004        mMinMode = PIXELS;
2005
2006        requestLayout();
2007        invalidate();
2008    }
2009
2010    /**
2011     * Makes the TextView at most this many lines tall
2012     *
2013     * @attr ref android.R.styleable#TextView_maxLines
2014     */
2015    @android.view.RemotableViewMethod
2016    public void setMaxLines(int maxlines) {
2017        mMaximum = maxlines;
2018        mMaxMode = LINES;
2019
2020        requestLayout();
2021        invalidate();
2022    }
2023
2024    /**
2025     * Makes the TextView at most this many pixels tall
2026     *
2027     * @attr ref android.R.styleable#TextView_maxHeight
2028     */
2029    @android.view.RemotableViewMethod
2030    public void setMaxHeight(int maxHeight) {
2031        mMaximum = maxHeight;
2032        mMaxMode = PIXELS;
2033
2034        requestLayout();
2035        invalidate();
2036    }
2037
2038    /**
2039     * Makes the TextView exactly this many lines tall
2040     *
2041     * @attr ref android.R.styleable#TextView_lines
2042     */
2043    @android.view.RemotableViewMethod
2044    public void setLines(int lines) {
2045        mMaximum = mMinimum = lines;
2046        mMaxMode = mMinMode = LINES;
2047
2048        requestLayout();
2049        invalidate();
2050    }
2051
2052    /**
2053     * Makes the TextView exactly this many pixels tall.
2054     * You could do the same thing by specifying this number in the
2055     * LayoutParams.
2056     *
2057     * @attr ref android.R.styleable#TextView_height
2058     */
2059    @android.view.RemotableViewMethod
2060    public void setHeight(int pixels) {
2061        mMaximum = mMinimum = pixels;
2062        mMaxMode = mMinMode = PIXELS;
2063
2064        requestLayout();
2065        invalidate();
2066    }
2067
2068    /**
2069     * Makes the TextView at least this many ems wide
2070     *
2071     * @attr ref android.R.styleable#TextView_minEms
2072     */
2073    @android.view.RemotableViewMethod
2074    public void setMinEms(int minems) {
2075        mMinWidth = minems;
2076        mMinWidthMode = EMS;
2077
2078        requestLayout();
2079        invalidate();
2080    }
2081
2082    /**
2083     * Makes the TextView at least this many pixels wide
2084     *
2085     * @attr ref android.R.styleable#TextView_minWidth
2086     */
2087    @android.view.RemotableViewMethod
2088    public void setMinWidth(int minpixels) {
2089        mMinWidth = minpixels;
2090        mMinWidthMode = PIXELS;
2091
2092        requestLayout();
2093        invalidate();
2094    }
2095
2096    /**
2097     * Makes the TextView at most this many ems wide
2098     *
2099     * @attr ref android.R.styleable#TextView_maxEms
2100     */
2101    @android.view.RemotableViewMethod
2102    public void setMaxEms(int maxems) {
2103        mMaxWidth = maxems;
2104        mMaxWidthMode = EMS;
2105
2106        requestLayout();
2107        invalidate();
2108    }
2109
2110    /**
2111     * Makes the TextView at most this many pixels wide
2112     *
2113     * @attr ref android.R.styleable#TextView_maxWidth
2114     */
2115    @android.view.RemotableViewMethod
2116    public void setMaxWidth(int maxpixels) {
2117        mMaxWidth = maxpixels;
2118        mMaxWidthMode = PIXELS;
2119
2120        requestLayout();
2121        invalidate();
2122    }
2123
2124    /**
2125     * Makes the TextView exactly this many ems wide
2126     *
2127     * @attr ref android.R.styleable#TextView_ems
2128     */
2129    @android.view.RemotableViewMethod
2130    public void setEms(int ems) {
2131        mMaxWidth = mMinWidth = ems;
2132        mMaxWidthMode = mMinWidthMode = EMS;
2133
2134        requestLayout();
2135        invalidate();
2136    }
2137
2138    /**
2139     * Makes the TextView exactly this many pixels wide.
2140     * You could do the same thing by specifying this number in the
2141     * LayoutParams.
2142     *
2143     * @attr ref android.R.styleable#TextView_width
2144     */
2145    @android.view.RemotableViewMethod
2146    public void setWidth(int pixels) {
2147        mMaxWidth = mMinWidth = pixels;
2148        mMaxWidthMode = mMinWidthMode = PIXELS;
2149
2150        requestLayout();
2151        invalidate();
2152    }
2153
2154
2155    /**
2156     * Sets line spacing for this TextView.  Each line will have its height
2157     * multiplied by <code>mult</code> and have <code>add</code> added to it.
2158     *
2159     * @attr ref android.R.styleable#TextView_lineSpacingExtra
2160     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
2161     */
2162    public void setLineSpacing(float add, float mult) {
2163        mSpacingMult = mult;
2164        mSpacingAdd = add;
2165
2166        if (mLayout != null) {
2167            nullLayouts();
2168            requestLayout();
2169            invalidate();
2170        }
2171    }
2172
2173    /**
2174     * Convenience method: Append the specified text to the TextView's
2175     * display buffer, upgrading it to BufferType.EDITABLE if it was
2176     * not already editable.
2177     */
2178    public final void append(CharSequence text) {
2179        append(text, 0, text.length());
2180    }
2181
2182    /**
2183     * Convenience method: Append the specified text slice to the TextView's
2184     * display buffer, upgrading it to BufferType.EDITABLE if it was
2185     * not already editable.
2186     */
2187    public void append(CharSequence text, int start, int end) {
2188        if (!(mText instanceof Editable)) {
2189            setText(mText, BufferType.EDITABLE);
2190        }
2191
2192        ((Editable) mText).append(text, start, end);
2193    }
2194
2195    private void updateTextColors() {
2196        boolean inval = false;
2197        int color = mTextColor.getColorForState(getDrawableState(), 0);
2198        if (color != mCurTextColor) {
2199            mCurTextColor = color;
2200            inval = true;
2201        }
2202        if (mLinkTextColor != null) {
2203            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
2204            if (color != mTextPaint.linkColor) {
2205                mTextPaint.linkColor = color;
2206                inval = true;
2207            }
2208        }
2209        if (mHintTextColor != null) {
2210            color = mHintTextColor.getColorForState(getDrawableState(), 0);
2211            if (color != mCurHintTextColor && mText.length() == 0) {
2212                mCurHintTextColor = color;
2213                inval = true;
2214            }
2215        }
2216        if (inval) {
2217            invalidate();
2218        }
2219    }
2220
2221    @Override
2222    protected void drawableStateChanged() {
2223        super.drawableStateChanged();
2224        if (mTextColor != null && mTextColor.isStateful()
2225                || (mHintTextColor != null && mHintTextColor.isStateful())
2226                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
2227            updateTextColors();
2228        }
2229
2230        final Drawables dr = mDrawables;
2231        if (dr != null) {
2232            int[] state = getDrawableState();
2233            if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
2234                dr.mDrawableTop.setState(state);
2235            }
2236            if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
2237                dr.mDrawableBottom.setState(state);
2238            }
2239            if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
2240                dr.mDrawableLeft.setState(state);
2241            }
2242            if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
2243                dr.mDrawableRight.setState(state);
2244            }
2245        }
2246    }
2247
2248    /**
2249     * User interface state that is stored by TextView for implementing
2250     * {@link View#onSaveInstanceState}.
2251     */
2252    public static class SavedState extends BaseSavedState {
2253        int selStart;
2254        int selEnd;
2255        CharSequence text;
2256        boolean frozenWithFocus;
2257        CharSequence error;
2258
2259        SavedState(Parcelable superState) {
2260            super(superState);
2261        }
2262
2263        @Override
2264        public void writeToParcel(Parcel out, int flags) {
2265            super.writeToParcel(out, flags);
2266            out.writeInt(selStart);
2267            out.writeInt(selEnd);
2268            out.writeInt(frozenWithFocus ? 1 : 0);
2269            TextUtils.writeToParcel(text, out, flags);
2270
2271            if (error == null) {
2272                out.writeInt(0);
2273            } else {
2274                out.writeInt(1);
2275                TextUtils.writeToParcel(error, out, flags);
2276            }
2277        }
2278
2279        @Override
2280        public String toString() {
2281            String str = "TextView.SavedState{"
2282                    + Integer.toHexString(System.identityHashCode(this))
2283                    + " start=" + selStart + " end=" + selEnd;
2284            if (text != null) {
2285                str += " text=" + text;
2286            }
2287            return str + "}";
2288        }
2289
2290        public static final Parcelable.Creator<SavedState> CREATOR
2291                = new Parcelable.Creator<SavedState>() {
2292            public SavedState createFromParcel(Parcel in) {
2293                return new SavedState(in);
2294            }
2295
2296            public SavedState[] newArray(int size) {
2297                return new SavedState[size];
2298            }
2299        };
2300
2301        private SavedState(Parcel in) {
2302            super(in);
2303            selStart = in.readInt();
2304            selEnd = in.readInt();
2305            frozenWithFocus = (in.readInt() != 0);
2306            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2307
2308            if (in.readInt() != 0) {
2309                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2310            }
2311        }
2312    }
2313
2314    @Override
2315    public Parcelable onSaveInstanceState() {
2316        Parcelable superState = super.onSaveInstanceState();
2317
2318        // Save state if we are forced to
2319        boolean save = mFreezesText;
2320        int start = 0;
2321        int end = 0;
2322
2323        if (mText != null) {
2324            start = Selection.getSelectionStart(mText);
2325            end = Selection.getSelectionEnd(mText);
2326            if (start >= 0 || end >= 0) {
2327                // Or save state if there is a selection
2328                save = true;
2329            }
2330        }
2331
2332        if (save) {
2333            SavedState ss = new SavedState(superState);
2334            // XXX Should also save the current scroll position!
2335            ss.selStart = start;
2336            ss.selEnd = end;
2337
2338            if (mText instanceof Spanned) {
2339                /*
2340                 * Calling setText() strips off any ChangeWatchers;
2341                 * strip them now to avoid leaking references.
2342                 * But do it to a copy so that if there are any
2343                 * further changes to the text of this view, it
2344                 * won't get into an inconsistent state.
2345                 */
2346
2347                Spannable sp = new SpannableString(mText);
2348
2349                for (ChangeWatcher cw :
2350                     sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2351                    sp.removeSpan(cw);
2352                }
2353
2354                ss.text = sp;
2355            } else {
2356                ss.text = mText.toString();
2357            }
2358
2359            if (isFocused() && start >= 0 && end >= 0) {
2360                ss.frozenWithFocus = true;
2361            }
2362
2363            ss.error = mError;
2364
2365            return ss;
2366        }
2367
2368        return superState;
2369    }
2370
2371    @Override
2372    public void onRestoreInstanceState(Parcelable state) {
2373        if (!(state instanceof SavedState)) {
2374            super.onRestoreInstanceState(state);
2375            return;
2376        }
2377
2378        SavedState ss = (SavedState)state;
2379        super.onRestoreInstanceState(ss.getSuperState());
2380
2381        // XXX restore buffer type too, as well as lots of other stuff
2382        if (ss.text != null) {
2383            setText(ss.text);
2384        }
2385
2386        if (ss.selStart >= 0 && ss.selEnd >= 0) {
2387            if (mText instanceof Spannable) {
2388                int len = mText.length();
2389
2390                if (ss.selStart > len || ss.selEnd > len) {
2391                    String restored = "";
2392
2393                    if (ss.text != null) {
2394                        restored = "(restored) ";
2395                    }
2396
2397                    Log.e("TextView", "Saved cursor position " + ss.selStart +
2398                          "/" + ss.selEnd + " out of range for " + restored +
2399                          "text " + mText);
2400                } else {
2401                    Selection.setSelection((Spannable) mText, ss.selStart,
2402                                           ss.selEnd);
2403
2404                    if (ss.frozenWithFocus) {
2405                        mFrozenWithFocus = true;
2406                    }
2407                }
2408            }
2409        }
2410
2411        if (ss.error != null) {
2412            setError(ss.error);
2413        }
2414    }
2415
2416    /**
2417     * Control whether this text view saves its entire text contents when
2418     * freezing to an icicle, in addition to dynamic state such as cursor
2419     * position.  By default this is false, not saving the text.  Set to true
2420     * if the text in the text view is not being saved somewhere else in
2421     * persistent storage (such as in a content provider) so that if the
2422     * view is later thawed the user will not lose their data.
2423     *
2424     * @param freezesText Controls whether a frozen icicle should include the
2425     * entire text data: true to include it, false to not.
2426     *
2427     * @attr ref android.R.styleable#TextView_freezesText
2428     */
2429    @android.view.RemotableViewMethod
2430    public void setFreezesText(boolean freezesText) {
2431        mFreezesText = freezesText;
2432    }
2433
2434    /**
2435     * Return whether this text view is including its entire text contents
2436     * in frozen icicles.
2437     *
2438     * @return Returns true if text is included, false if it isn't.
2439     *
2440     * @see #setFreezesText
2441     */
2442    public boolean getFreezesText() {
2443        return mFreezesText;
2444    }
2445
2446    ///////////////////////////////////////////////////////////////////////////
2447
2448    /**
2449     * Sets the Factory used to create new Editables.
2450     */
2451    public final void setEditableFactory(Editable.Factory factory) {
2452        mEditableFactory = factory;
2453        setText(mText);
2454    }
2455
2456    /**
2457     * Sets the Factory used to create new Spannables.
2458     */
2459    public final void setSpannableFactory(Spannable.Factory factory) {
2460        mSpannableFactory = factory;
2461        setText(mText);
2462    }
2463
2464    /**
2465     * Sets the string value of the TextView. TextView <em>does not</em> accept
2466     * HTML-like formatting, which you can do with text strings in XML resource files.
2467     * To style your strings, attach android.text.style.* objects to a
2468     * {@link android.text.SpannableString SpannableString}, or see the
2469     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
2470     * Available Resource Types</a> documentation for an example of setting
2471     * formatted text in the XML resource file.
2472     *
2473     * @attr ref android.R.styleable#TextView_text
2474     */
2475    @android.view.RemotableViewMethod
2476    public final void setText(CharSequence text) {
2477        setText(text, mBufferType);
2478    }
2479
2480    /**
2481     * Like {@link #setText(CharSequence)},
2482     * except that the cursor position (if any) is retained in the new text.
2483     *
2484     * @param text The new text to place in the text view.
2485     *
2486     * @see #setText(CharSequence)
2487     */
2488    @android.view.RemotableViewMethod
2489    public final void setTextKeepState(CharSequence text) {
2490        setTextKeepState(text, mBufferType);
2491    }
2492
2493    /**
2494     * Sets the text that this TextView is to display (see
2495     * {@link #setText(CharSequence)}) and also sets whether it is stored
2496     * in a styleable/spannable buffer and whether it is editable.
2497     *
2498     * @attr ref android.R.styleable#TextView_text
2499     * @attr ref android.R.styleable#TextView_bufferType
2500     */
2501    public void setText(CharSequence text, BufferType type) {
2502        setText(text, type, true, 0);
2503
2504        if (mCharWrapper != null) {
2505            mCharWrapper.mChars = null;
2506        }
2507    }
2508
2509    private void setText(CharSequence text, BufferType type,
2510                         boolean notifyBefore, int oldlen) {
2511        if (text == null) {
2512            text = "";
2513        }
2514
2515        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
2516
2517        if (text instanceof Spanned &&
2518            ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
2519            setHorizontalFadingEdgeEnabled(true);
2520            setEllipsize(TextUtils.TruncateAt.MARQUEE);
2521        }
2522
2523        int n = mFilters.length;
2524        for (int i = 0; i < n; i++) {
2525            CharSequence out = mFilters[i].filter(text, 0, text.length(),
2526                                                  EMPTY_SPANNED, 0, 0);
2527            if (out != null) {
2528                text = out;
2529            }
2530        }
2531
2532        if (notifyBefore) {
2533            if (mText != null) {
2534                oldlen = mText.length();
2535                sendBeforeTextChanged(mText, 0, oldlen, text.length());
2536            } else {
2537                sendBeforeTextChanged("", 0, 0, text.length());
2538            }
2539        }
2540
2541        boolean needEditableForNotification = false;
2542
2543        if (mListeners != null && mListeners.size() != 0) {
2544            needEditableForNotification = true;
2545        }
2546
2547        if (type == BufferType.EDITABLE || mInput != null ||
2548            needEditableForNotification) {
2549            Editable t = mEditableFactory.newEditable(text);
2550            text = t;
2551            setFilters(t, mFilters);
2552            InputMethodManager imm = InputMethodManager.peekInstance();
2553            if (imm != null) imm.restartInput(this);
2554        } else if (type == BufferType.SPANNABLE || mMovement != null) {
2555            text = mSpannableFactory.newSpannable(text);
2556        } else if (!(text instanceof CharWrapper)) {
2557            text = TextUtils.stringOrSpannedString(text);
2558        }
2559
2560        if (mAutoLinkMask != 0) {
2561            Spannable s2;
2562
2563            if (type == BufferType.EDITABLE || text instanceof Spannable) {
2564                s2 = (Spannable) text;
2565            } else {
2566                s2 = mSpannableFactory.newSpannable(text);
2567            }
2568
2569            if (Linkify.addLinks(s2, mAutoLinkMask)) {
2570                text = s2;
2571                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
2572
2573                /*
2574                 * We must go ahead and set the text before changing the
2575                 * movement method, because setMovementMethod() may call
2576                 * setText() again to try to upgrade the buffer type.
2577                 */
2578                mText = text;
2579
2580                if (mLinksClickable) {
2581                    setMovementMethod(LinkMovementMethod.getInstance());
2582                }
2583            }
2584        }
2585
2586        mBufferType = type;
2587        mText = text;
2588
2589        if (mTransformation == null)
2590            mTransformed = text;
2591        else
2592            mTransformed = mTransformation.getTransformation(text, this);
2593
2594        final int textLength = text.length();
2595
2596        if (text instanceof Spannable) {
2597            Spannable sp = (Spannable) text;
2598
2599            // Remove any ChangeWatchers that might have come
2600            // from other TextViews.
2601            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
2602            final int count = watchers.length;
2603            for (int i = 0; i < count; i++)
2604                sp.removeSpan(watchers[i]);
2605
2606            if (mChangeWatcher == null)
2607                mChangeWatcher = new ChangeWatcher();
2608
2609            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
2610                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
2611
2612            if (mInput != null) {
2613                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2614            }
2615
2616            if (mTransformation != null) {
2617                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2618
2619            }
2620
2621            if (mMovement != null) {
2622                mMovement.initialize(this, (Spannable) text);
2623
2624                /*
2625                 * Initializing the movement method will have set the
2626                 * selection, so reset mSelectionMoved to keep that from
2627                 * interfering with the normal on-focus selection-setting.
2628                 */
2629                mSelectionMoved = false;
2630            }
2631        }
2632
2633        if (mLayout != null) {
2634            checkForRelayout();
2635        }
2636
2637        sendOnTextChanged(text, 0, oldlen, textLength);
2638        onTextChanged(text, 0, oldlen, textLength);
2639
2640        if (needEditableForNotification) {
2641            sendAfterTextChanged((Editable) text);
2642        }
2643    }
2644
2645    /**
2646     * Sets the TextView to display the specified slice of the specified
2647     * char array.  You must promise that you will not change the contents
2648     * of the array except for right before another call to setText(),
2649     * since the TextView has no way to know that the text
2650     * has changed and that it needs to invalidate and re-layout.
2651     */
2652    public final void setText(char[] text, int start, int len) {
2653        int oldlen = 0;
2654
2655        if (start < 0 || len < 0 || start + len > text.length) {
2656            throw new IndexOutOfBoundsException(start + ", " + len);
2657        }
2658
2659        /*
2660         * We must do the before-notification here ourselves because if
2661         * the old text is a CharWrapper we destroy it before calling
2662         * into the normal path.
2663         */
2664        if (mText != null) {
2665            oldlen = mText.length();
2666            sendBeforeTextChanged(mText, 0, oldlen, len);
2667        } else {
2668            sendBeforeTextChanged("", 0, 0, len);
2669        }
2670
2671        if (mCharWrapper == null) {
2672            mCharWrapper = new CharWrapper(text, start, len);
2673        } else {
2674            mCharWrapper.set(text, start, len);
2675        }
2676
2677        setText(mCharWrapper, mBufferType, false, oldlen);
2678    }
2679
2680    private static class CharWrapper
2681            implements CharSequence, GetChars, GraphicsOperations {
2682        private char[] mChars;
2683        private int mStart, mLength;
2684
2685        public CharWrapper(char[] chars, int start, int len) {
2686            mChars = chars;
2687            mStart = start;
2688            mLength = len;
2689        }
2690
2691        /* package */ void set(char[] chars, int start, int len) {
2692            mChars = chars;
2693            mStart = start;
2694            mLength = len;
2695        }
2696
2697        public int length() {
2698            return mLength;
2699        }
2700
2701        public char charAt(int off) {
2702            return mChars[off + mStart];
2703        }
2704
2705        public String toString() {
2706            return new String(mChars, mStart, mLength);
2707        }
2708
2709        public CharSequence subSequence(int start, int end) {
2710            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2711                throw new IndexOutOfBoundsException(start + ", " + end);
2712            }
2713
2714            return new String(mChars, start + mStart, end - start);
2715        }
2716
2717        public void getChars(int start, int end, char[] buf, int off) {
2718            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2719                throw new IndexOutOfBoundsException(start + ", " + end);
2720            }
2721
2722            System.arraycopy(mChars, start + mStart, buf, off, end - start);
2723        }
2724
2725        public void drawText(Canvas c, int start, int end,
2726                             float x, float y, Paint p) {
2727            c.drawText(mChars, start + mStart, end - start, x, y, p);
2728        }
2729
2730        public float measureText(int start, int end, Paint p) {
2731            return p.measureText(mChars, start + mStart, end - start);
2732        }
2733
2734        public int getTextWidths(int start, int end, float[] widths, Paint p) {
2735            return p.getTextWidths(mChars, start + mStart, end - start, widths);
2736        }
2737    }
2738
2739    /**
2740     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2741     * except that the cursor position (if any) is retained in the new text.
2742     *
2743     * @see #setText(CharSequence, android.widget.TextView.BufferType)
2744     */
2745    public final void setTextKeepState(CharSequence text, BufferType type) {
2746        int start = getSelectionStart();
2747        int end = getSelectionEnd();
2748        int len = text.length();
2749
2750        setText(text, type);
2751
2752        if (start >= 0 || end >= 0) {
2753            if (mText instanceof Spannable) {
2754                Selection.setSelection((Spannable) mText,
2755                                       Math.max(0, Math.min(start, len)),
2756                                       Math.max(0, Math.min(end, len)));
2757            }
2758        }
2759    }
2760
2761    @android.view.RemotableViewMethod
2762    public final void setText(int resid) {
2763        setText(getContext().getResources().getText(resid));
2764    }
2765
2766    public final void setText(int resid, BufferType type) {
2767        setText(getContext().getResources().getText(resid), type);
2768    }
2769
2770    /**
2771     * Sets the text to be displayed when the text of the TextView is empty.
2772     * Null means to use the normal empty text. The hint does not currently
2773     * participate in determining the size of the view.
2774     *
2775     * This method is deprecated. Use {link #setHint(int, String)} or
2776     * {link #setHint(CharSequence, String)} instead.
2777     *
2778     * @attr ref android.R.styleable#TextView_hint
2779     */
2780    @android.view.RemotableViewMethod
2781    public final void setHint(CharSequence hint) {
2782        mHint = TextUtils.stringOrSpannedString(hint);
2783
2784        if (mLayout != null) {
2785            checkForRelayout();
2786        }
2787
2788        if (mText.length() == 0)
2789            invalidate();
2790    }
2791
2792    /**
2793     * Sets the text to be displayed when the text of the TextView is empty,
2794     * from a resource.
2795     *
2796     * This method is deprecated. Use {link #setHint(int, String)} or
2797     * {link #setHint(CharSequence, String)} instead.
2798     *
2799     * @attr ref android.R.styleable#TextView_hint
2800     */
2801    @android.view.RemotableViewMethod
2802    public final void setHint(int resid) {
2803        setHint(getContext().getResources().getText(resid));
2804    }
2805
2806    /**
2807     * Returns the hint that is displayed when the text of the TextView
2808     * is empty.
2809     *
2810     * @attr ref android.R.styleable#TextView_hint
2811     */
2812    @ViewDebug.CapturedViewProperty
2813    public CharSequence getHint() {
2814        return mHint;
2815    }
2816
2817    /**
2818     * Set the type of the content with a constant as defined for
2819     * {@link EditorInfo#inputType}.  This will take care of changing
2820     * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
2821     * match the given content type.  If the given content type is
2822     * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
2823     * not be displayed for this text view.
2824     *
2825     * @see #getInputType()
2826     * @see #setRawInputType(int)
2827     * @see android.text.InputType
2828     * @attr ref android.R.styleable#TextView_inputType
2829     */
2830    public void setInputType(int type) {
2831        setInputType(type, false);
2832        final int variation = type&(EditorInfo.TYPE_MASK_CLASS
2833                |EditorInfo.TYPE_MASK_VARIATION);
2834        final boolean isPassword = variation
2835                == (EditorInfo.TYPE_CLASS_TEXT
2836                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
2837        boolean forceUpdate = false;
2838        if (isPassword) {
2839            setTransformationMethod(PasswordTransformationMethod.getInstance());
2840            setTypefaceByIndex(MONOSPACE, 0);
2841        } else if (mTransformation == PasswordTransformationMethod.getInstance()) {
2842            // We need to clean up if we were previously in password mode.
2843            if (variation != (EditorInfo.TYPE_CLASS_TEXT
2844                        |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
2845                setTypefaceByIndex(-1, -1);
2846            }
2847            forceUpdate = true;
2848        } else if (variation == (EditorInfo.TYPE_CLASS_TEXT
2849                        |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
2850            setTypefaceByIndex(MONOSPACE, 0);
2851        }
2852
2853        boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
2854                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
2855                (EditorInfo.TYPE_CLASS_TEXT
2856                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
2857
2858        // We need to update the single line mode if it has changed or we
2859        // were previously in password mode.
2860        if (mSingleLine == multiLine || forceUpdate) {
2861            // Change single line mode, but only change the transformation if
2862            // we are not in password mode.
2863            applySingleLine(!multiLine, !isPassword);
2864        }
2865
2866        InputMethodManager imm = InputMethodManager.peekInstance();
2867        if (imm != null) imm.restartInput(this);
2868    }
2869
2870    /**
2871     * Directly change the content type integer of the text view, without
2872     * modifying any other state.
2873     * @see #setInputType(int)
2874     * @see android.text.InputType
2875     * @attr ref android.R.styleable#TextView_inputType
2876     */
2877    public void setRawInputType(int type) {
2878        mInputType = type;
2879    }
2880
2881    private void setInputType(int type, boolean direct) {
2882        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
2883        KeyListener input;
2884        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
2885            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
2886                    != 0;
2887            TextKeyListener.Capitalize cap;
2888            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
2889                cap = TextKeyListener.Capitalize.CHARACTERS;
2890            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
2891                cap = TextKeyListener.Capitalize.WORDS;
2892            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
2893                cap = TextKeyListener.Capitalize.SENTENCES;
2894            } else {
2895                cap = TextKeyListener.Capitalize.NONE;
2896            }
2897            input = TextKeyListener.getInstance(autotext, cap);
2898        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
2899            input = DigitsKeyListener.getInstance(
2900                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
2901                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
2902        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
2903            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
2904                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
2905                    input = DateKeyListener.getInstance();
2906                    break;
2907                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
2908                    input = TimeKeyListener.getInstance();
2909                    break;
2910                default:
2911                    input = DateTimeKeyListener.getInstance();
2912                    break;
2913            }
2914        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
2915            input = DialerKeyListener.getInstance();
2916        } else {
2917            input = TextKeyListener.getInstance();
2918        }
2919        mInputType = type;
2920        if (direct) mInput = input;
2921        else {
2922            setKeyListenerOnly(input);
2923        }
2924    }
2925
2926    /**
2927     * Get the type of the content.
2928     *
2929     * @see #setInputType(int)
2930     * @see android.text.InputType
2931     */
2932    public int getInputType() {
2933        return mInputType;
2934    }
2935
2936    /**
2937     * Change the editor type integer associated with the text view, which
2938     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
2939     * has focus.
2940     * @see #getImeOptions
2941     * @see android.view.inputmethod.EditorInfo
2942     * @attr ref android.R.styleable#TextView_imeOptions
2943     */
2944    public void setImeOptions(int imeOptions) {
2945        if (mInputContentType == null) {
2946            mInputContentType = new InputContentType();
2947        }
2948        mInputContentType.imeOptions = imeOptions;
2949    }
2950
2951    /**
2952     * Get the type of the IME editor.
2953     *
2954     * @see #setImeOptions(int)
2955     * @see android.view.inputmethod.EditorInfo
2956     */
2957    public int getImeOptions() {
2958        return mInputContentType != null
2959                ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
2960    }
2961
2962    /**
2963     * Change the custom IME action associated with the text view, which
2964     * will be reported to an IME with {@link EditorInfo#actionLabel}
2965     * and {@link EditorInfo#actionId} when it has focus.
2966     * @see #getImeActionLabel
2967     * @see #getImeActionId
2968     * @see android.view.inputmethod.EditorInfo
2969     * @attr ref android.R.styleable#TextView_imeActionLabel
2970     * @attr ref android.R.styleable#TextView_imeActionId
2971     */
2972    public void setImeActionLabel(CharSequence label, int actionId) {
2973        if (mInputContentType == null) {
2974            mInputContentType = new InputContentType();
2975        }
2976        mInputContentType.imeActionLabel = label;
2977        mInputContentType.imeActionId = actionId;
2978    }
2979
2980    /**
2981     * Get the IME action label previous set with {@link #setImeActionLabel}.
2982     *
2983     * @see #setImeActionLabel
2984     * @see android.view.inputmethod.EditorInfo
2985     */
2986    public CharSequence getImeActionLabel() {
2987        return mInputContentType != null
2988                ? mInputContentType.imeActionLabel : null;
2989    }
2990
2991    /**
2992     * Get the IME action ID previous set with {@link #setImeActionLabel}.
2993     *
2994     * @see #setImeActionLabel
2995     * @see android.view.inputmethod.EditorInfo
2996     */
2997    public int getImeActionId() {
2998        return mInputContentType != null
2999                ? mInputContentType.imeActionId : 0;
3000    }
3001
3002    /**
3003     * Set a special listener to be called when an action is performed
3004     * on the text view.  This will be called when the enter key is pressed,
3005     * or when an action supplied to the IME is selected by the user.  Setting
3006     * this means that the normal hard key event will not insert a newline
3007     * into the text view, even if it is multi-line; holding down the ALT
3008     * modifier will, however, allow the user to insert a newline character.
3009     */
3010    public void setOnEditorActionListener(OnEditorActionListener l) {
3011        if (mInputContentType == null) {
3012            mInputContentType = new InputContentType();
3013        }
3014        mInputContentType.onEditorActionListener = l;
3015    }
3016
3017    /**
3018     * Called when an attached input method calls
3019     * {@link InputConnection#performEditorAction(int)
3020     * InputConnection.performEditorAction()}
3021     * for this text view.  The default implementation will call your action
3022     * listener supplied to {@link #setOnEditorActionListener}, or perform
3023     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
3024     * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
3025     * EditorInfo.IME_ACTION_DONE}.
3026     *
3027     * <p>For backwards compatibility, if no IME options have been set and the
3028     * text view would not normally advance focus on enter, then
3029     * the NEXT and DONE actions received here will be turned into an enter
3030     * key down/up pair to go through the normal key handling.
3031     *
3032     * @param actionCode The code of the action being performed.
3033     *
3034     * @see #setOnEditorActionListener
3035     */
3036    public void onEditorAction(int actionCode) {
3037        final InputContentType ict = mInputContentType;
3038        if (ict != null) {
3039            if (ict.onEditorActionListener != null) {
3040                if (ict.onEditorActionListener.onEditorAction(this,
3041                        actionCode, null)) {
3042                    return;
3043                }
3044            }
3045
3046            // This is the handling for some default action.
3047            // Note that for backwards compatibility we don't do this
3048            // default handling if explicit ime options have not been given,
3049            // instead turning this into the normal enter key codes that an
3050            // app may be expecting.
3051            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3052                View v = focusSearch(FOCUS_DOWN);
3053                if (v != null) {
3054                    if (!v.requestFocus(FOCUS_DOWN)) {
3055                        throw new IllegalStateException("focus search returned a view " +
3056                                "that wasn't able to take focus!");
3057                    }
3058                }
3059                return;
3060
3061            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3062                InputMethodManager imm = InputMethodManager.peekInstance();
3063                if (imm != null) {
3064                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
3065                }
3066                return;
3067            }
3068        }
3069
3070        Handler h = getHandler();
3071        if (h != null) {
3072            long eventTime = SystemClock.uptimeMillis();
3073            h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3074                    new KeyEvent(eventTime, eventTime,
3075                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3076                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3077                    | KeyEvent.FLAG_EDITOR_ACTION)));
3078            h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3079                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
3080                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3081                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3082                    | KeyEvent.FLAG_EDITOR_ACTION)));
3083        }
3084    }
3085
3086    /**
3087     * Set the private content type of the text, which is the
3088     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3089     * field that will be filled in when creating an input connection.
3090     *
3091     * @see #getPrivateImeOptions()
3092     * @see EditorInfo#privateImeOptions
3093     * @attr ref android.R.styleable#TextView_privateImeOptions
3094     */
3095    public void setPrivateImeOptions(String type) {
3096        if (mInputContentType == null) mInputContentType = new InputContentType();
3097        mInputContentType.privateImeOptions = type;
3098    }
3099
3100    /**
3101     * Get the private type of the content.
3102     *
3103     * @see #setPrivateImeOptions(String)
3104     * @see EditorInfo#privateImeOptions
3105     */
3106    public String getPrivateImeOptions() {
3107        return mInputContentType != null
3108                ? mInputContentType.privateImeOptions : null;
3109    }
3110
3111    /**
3112     * Set the extra input data of the text, which is the
3113     * {@link EditorInfo#extras TextBoxAttribute.extras}
3114     * Bundle that will be filled in when creating an input connection.  The
3115     * given integer is the resource ID of an XML resource holding an
3116     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3117     *
3118     * @see #getInputExtras(boolean)
3119     * @see EditorInfo#extras
3120     * @attr ref android.R.styleable#TextView_editorExtras
3121     */
3122    public void setInputExtras(int xmlResId)
3123            throws XmlPullParserException, IOException {
3124        XmlResourceParser parser = getResources().getXml(xmlResId);
3125        if (mInputContentType == null) mInputContentType = new InputContentType();
3126        mInputContentType.extras = new Bundle();
3127        getResources().parseBundleExtras(parser, mInputContentType.extras);
3128    }
3129
3130    /**
3131     * Retrieve the input extras currently associated with the text view, which
3132     * can be viewed as well as modified.
3133     *
3134     * @param create If true, the extras will be created if they don't already
3135     * exist.  Otherwise, null will be returned if none have been created.
3136     * @see #setInputExtras(int)View
3137     * @see EditorInfo#extras
3138     * @attr ref android.R.styleable#TextView_editorExtras
3139     */
3140    public Bundle getInputExtras(boolean create) {
3141        if (mInputContentType == null) {
3142            if (!create) return null;
3143            mInputContentType = new InputContentType();
3144        }
3145        if (mInputContentType.extras == null) {
3146            if (!create) return null;
3147            mInputContentType.extras = new Bundle();
3148        }
3149        return mInputContentType.extras;
3150    }
3151
3152    /**
3153     * Returns the error message that was set to be displayed with
3154     * {@link #setError}, or <code>null</code> if no error was set
3155     * or if it the error was cleared by the widget after user input.
3156     */
3157    public CharSequence getError() {
3158        return mError;
3159    }
3160
3161    /**
3162     * Sets the right-hand compound drawable of the TextView to the "error"
3163     * icon and sets an error message that will be displayed in a popup when
3164     * the TextView has focus.  The icon and error message will be reset to
3165     * null when any key events cause changes to the TextView's text.  If the
3166     * <code>error</code> is <code>null</code>, the error message and icon
3167     * will be cleared.
3168     */
3169    @android.view.RemotableViewMethod
3170    public void setError(CharSequence error) {
3171        if (error == null) {
3172            setError(null, null);
3173        } else {
3174            Drawable dr = getContext().getResources().
3175                getDrawable(com.android.internal.R.drawable.
3176                            indicator_input_error);
3177
3178            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3179            setError(error, dr);
3180        }
3181    }
3182
3183    /**
3184     * Sets the right-hand compound drawable of the TextView to the specified
3185     * icon and sets an error message that will be displayed in a popup when
3186     * the TextView has focus.  The icon and error message will be reset to
3187     * null when any key events cause changes to the TextView's text.  The
3188     * drawable must already have had {@link Drawable#setBounds} set on it.
3189     * If the <code>error</code> is <code>null</code>, the error message will
3190     * be cleared (and you should provide a <code>null</code> icon as well).
3191     */
3192    public void setError(CharSequence error, Drawable icon) {
3193        error = TextUtils.stringOrSpannedString(error);
3194
3195        mError = error;
3196        mErrorWasChanged = true;
3197        final Drawables dr = mDrawables;
3198        if (dr != null) {
3199            setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
3200                                 icon, dr.mDrawableBottom);
3201        } else {
3202            setCompoundDrawables(null, null, icon, null);
3203        }
3204
3205        if (error == null) {
3206            if (mPopup != null) {
3207                if (mPopup.isShowing()) {
3208                    mPopup.dismiss();
3209                }
3210
3211                mPopup = null;
3212            }
3213        } else {
3214            if (isFocused()) {
3215                showError();
3216            }
3217        }
3218    }
3219
3220    private void showError() {
3221        if (getWindowToken() == null) {
3222            mShowErrorAfterAttach = true;
3223            return;
3224        }
3225
3226        if (mPopup == null) {
3227            LayoutInflater inflater = LayoutInflater.from(getContext());
3228            final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
3229                    null);
3230
3231            mPopup = new ErrorPopup(err, 200, 50);
3232            mPopup.setFocusable(false);
3233            // The user is entering text, so the input method is needed.  We
3234            // don't want the popup to be displayed on top of it.
3235            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
3236        }
3237
3238        TextView tv = (TextView) mPopup.getContentView();
3239        chooseSize(mPopup, mError, tv);
3240        tv.setText(mError);
3241
3242        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
3243        mPopup.fixDirection(mPopup.isAboveAnchor());
3244    }
3245
3246    private static class ErrorPopup extends PopupWindow {
3247        private boolean mAbove = false;
3248        private TextView mView;
3249
3250        ErrorPopup(TextView v, int width, int height) {
3251            super(v, width, height);
3252            mView = v;
3253        }
3254
3255        void fixDirection(boolean above) {
3256            mAbove = above;
3257
3258            if (above) {
3259                mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
3260            } else {
3261                mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
3262            }
3263        }
3264
3265        @Override
3266        public void update(int x, int y, int w, int h, boolean force) {
3267            super.update(x, y, w, h, force);
3268
3269            boolean above = isAboveAnchor();
3270            if (above != mAbove) {
3271                fixDirection(above);
3272            }
3273        }
3274    }
3275
3276    /**
3277     * Returns the Y offset to make the pointy top of the error point
3278     * at the middle of the error icon.
3279     */
3280    private int getErrorX() {
3281        /*
3282         * The "25" is the distance between the point and the right edge
3283         * of the background
3284         */
3285
3286        final Drawables dr = mDrawables;
3287        return getWidth() - mPopup.getWidth()
3288                - getPaddingRight()
3289                - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25;
3290    }
3291
3292    /**
3293     * Returns the Y offset to make the pointy top of the error point
3294     * at the bottom of the error icon.
3295     */
3296    private int getErrorY() {
3297        /*
3298         * Compound, not extended, because the icon is not clipped
3299         * if the text height is smaller.
3300         */
3301        int vspace = mBottom - mTop -
3302                     getCompoundPaddingBottom() - getCompoundPaddingTop();
3303
3304        final Drawables dr = mDrawables;
3305        int icontop = getCompoundPaddingTop()
3306                + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
3307
3308        /*
3309         * The "2" is the distance between the point and the top edge
3310         * of the background.
3311         */
3312
3313        return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
3314                - getHeight() - 2;
3315    }
3316
3317    private void hideError() {
3318        if (mPopup != null) {
3319            if (mPopup.isShowing()) {
3320                mPopup.dismiss();
3321            }
3322        }
3323
3324        mShowErrorAfterAttach = false;
3325    }
3326
3327    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
3328        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
3329        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
3330
3331        /*
3332         * Figure out how big the text would be if we laid it out to the
3333         * full width of this view minus the border.
3334         */
3335        int cap = getWidth() - wid;
3336        if (cap < 0) {
3337            cap = 200; // We must not be measured yet -- setFrame() will fix it.
3338        }
3339
3340        Layout l = new StaticLayout(text, tv.getPaint(), cap,
3341                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
3342        float max = 0;
3343        for (int i = 0; i < l.getLineCount(); i++) {
3344            max = Math.max(max, l.getLineWidth(i));
3345        }
3346
3347        /*
3348         * Now set the popup size to be big enough for the text plus the border.
3349         */
3350        pop.setWidth(wid + (int) Math.ceil(max));
3351        pop.setHeight(ht + l.getHeight());
3352    }
3353
3354
3355    @Override
3356    protected boolean setFrame(int l, int t, int r, int b) {
3357        boolean result = super.setFrame(l, t, r, b);
3358
3359        if (mPopup != null) {
3360            TextView tv = (TextView) mPopup.getContentView();
3361            chooseSize(mPopup, mError, tv);
3362            mPopup.update(this, getErrorX(), getErrorY(), -1, -1);
3363        }
3364
3365        restartMarqueeIfNeeded();
3366
3367        return result;
3368    }
3369
3370    private void restartMarqueeIfNeeded() {
3371        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3372            mRestartMarquee = false;
3373            startMarquee();
3374        }
3375    }
3376
3377    /**
3378     * Sets the list of input filters that will be used if the buffer is
3379     * Editable.  Has no effect otherwise.
3380     *
3381     * @attr ref android.R.styleable#TextView_maxLength
3382     */
3383    public void setFilters(InputFilter[] filters) {
3384        if (filters == null) {
3385            throw new IllegalArgumentException();
3386        }
3387
3388        mFilters = filters;
3389
3390        if (mText instanceof Editable) {
3391            setFilters((Editable) mText, filters);
3392        }
3393    }
3394
3395    /**
3396     * Sets the list of input filters on the specified Editable,
3397     * and includes mInput in the list if it is an InputFilter.
3398     */
3399    private void setFilters(Editable e, InputFilter[] filters) {
3400        if (mInput instanceof InputFilter) {
3401            InputFilter[] nf = new InputFilter[filters.length + 1];
3402
3403            System.arraycopy(filters, 0, nf, 0, filters.length);
3404            nf[filters.length] = (InputFilter) mInput;
3405
3406            e.setFilters(nf);
3407        } else {
3408            e.setFilters(filters);
3409        }
3410    }
3411
3412    /**
3413     * Returns the current list of input filters.
3414     */
3415    public InputFilter[] getFilters() {
3416        return mFilters;
3417    }
3418
3419    /////////////////////////////////////////////////////////////////////////
3420
3421    private int getVerticalOffset(boolean forceNormal) {
3422        int voffset = 0;
3423        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3424
3425        Layout l = mLayout;
3426        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3427            l = mHintLayout;
3428        }
3429
3430        if (gravity != Gravity.TOP) {
3431            int boxht;
3432
3433            if (l == mHintLayout) {
3434                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3435                        getCompoundPaddingBottom();
3436            } else {
3437                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3438                        getExtendedPaddingBottom();
3439            }
3440            int textht = l.getHeight();
3441
3442            if (textht < boxht) {
3443                if (gravity == Gravity.BOTTOM)
3444                    voffset = boxht - textht;
3445                else // (gravity == Gravity.CENTER_VERTICAL)
3446                    voffset = (boxht - textht) >> 1;
3447            }
3448        }
3449        return voffset;
3450    }
3451
3452    private int getBottomVerticalOffset(boolean forceNormal) {
3453        int voffset = 0;
3454        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3455
3456        Layout l = mLayout;
3457        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3458            l = mHintLayout;
3459        }
3460
3461        if (gravity != Gravity.BOTTOM) {
3462            int boxht;
3463
3464            if (l == mHintLayout) {
3465                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3466                        getCompoundPaddingBottom();
3467            } else {
3468                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3469                        getExtendedPaddingBottom();
3470            }
3471            int textht = l.getHeight();
3472
3473            if (textht < boxht) {
3474                if (gravity == Gravity.TOP)
3475                    voffset = boxht - textht;
3476                else // (gravity == Gravity.CENTER_VERTICAL)
3477                    voffset = (boxht - textht) >> 1;
3478            }
3479        }
3480        return voffset;
3481    }
3482
3483    private void invalidateCursorPath() {
3484        if (mHighlightPathBogus) {
3485            invalidateCursor();
3486        } else {
3487            synchronized (sTempRect) {
3488                /*
3489                 * The reason for this concern about the thickness of the
3490                 * cursor and doing the floor/ceil on the coordinates is that
3491                 * some EditTexts (notably textfields in the Browser) have
3492                 * anti-aliased text where not all the characters are
3493                 * necessarily at integer-multiple locations.  This should
3494                 * make sure the entire cursor gets invalidated instead of
3495                 * sometimes missing half a pixel.
3496                 */
3497
3498                float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
3499                if (thick < 1.0f) {
3500                    thick = 1.0f;
3501                }
3502
3503                thick /= 2;
3504
3505                mHighlightPath.computeBounds(sTempRect, false);
3506
3507                int left = getCompoundPaddingLeft();
3508                int top = getExtendedPaddingTop() + getVerticalOffset(true);
3509
3510                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
3511                           (int) FloatMath.floor(top + sTempRect.top - thick),
3512                           (int) FloatMath.ceil(left + sTempRect.right + thick),
3513                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
3514            }
3515        }
3516    }
3517
3518    private void invalidateCursor() {
3519        int where = Selection.getSelectionEnd(mText);
3520
3521        invalidateCursor(where, where, where);
3522    }
3523
3524    private void invalidateCursor(int a, int b, int c) {
3525        if (mLayout == null) {
3526            invalidate();
3527        } else {
3528            if (a >= 0 || b >= 0 || c >= 0) {
3529                int first = Math.min(Math.min(a, b), c);
3530                int last = Math.max(Math.max(a, b), c);
3531
3532                int line = mLayout.getLineForOffset(first);
3533                int top = mLayout.getLineTop(line);
3534
3535                // This is ridiculous, but the descent from the line above
3536                // can hang down into the line we really want to redraw,
3537                // so we have to invalidate part of the line above to make
3538                // sure everything that needs to be redrawn really is.
3539                // (But not the whole line above, because that would cause
3540                // the same problem with the descenders on the line above it!)
3541                if (line > 0) {
3542                    top -= mLayout.getLineDescent(line - 1);
3543                }
3544
3545                int line2;
3546
3547                if (first == last)
3548                    line2 = line;
3549                else
3550                    line2 = mLayout.getLineForOffset(last);
3551
3552                int bottom = mLayout.getLineTop(line2 + 1);
3553                int voffset = getVerticalOffset(true);
3554
3555                int left = getCompoundPaddingLeft() + mScrollX;
3556                invalidate(left, top + voffset + getExtendedPaddingTop(),
3557                           left + getWidth() - getCompoundPaddingLeft() -
3558                           getCompoundPaddingRight(),
3559                           bottom + voffset + getExtendedPaddingTop());
3560            }
3561        }
3562    }
3563
3564    private void registerForPreDraw() {
3565        final ViewTreeObserver observer = getViewTreeObserver();
3566        if (observer == null) {
3567            return;
3568        }
3569
3570        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
3571            observer.addOnPreDrawListener(this);
3572            mPreDrawState = PREDRAW_PENDING;
3573        } else if (mPreDrawState == PREDRAW_DONE) {
3574            mPreDrawState = PREDRAW_PENDING;
3575        }
3576
3577        // else state is PREDRAW_PENDING, so keep waiting.
3578    }
3579
3580    /**
3581     * {@inheritDoc}
3582     */
3583    public boolean onPreDraw() {
3584        if (mPreDrawState != PREDRAW_PENDING) {
3585            return true;
3586        }
3587
3588        if (mLayout == null) {
3589            assumeLayout();
3590        }
3591
3592        boolean changed = false;
3593
3594        if (mMovement != null) {
3595            int curs = Selection.getSelectionEnd(mText);
3596
3597            /*
3598             * TODO: This should really only keep the end in view if
3599             * it already was before the text changed.  I'm not sure
3600             * of a good way to tell from here if it was.
3601             */
3602            if (curs < 0 &&
3603                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3604                curs = mText.length();
3605            }
3606
3607            if (curs >= 0) {
3608                changed = bringPointIntoView(curs);
3609            }
3610        } else {
3611            changed = bringTextIntoView();
3612        }
3613
3614        mPreDrawState = PREDRAW_DONE;
3615        return !changed;
3616    }
3617
3618    @Override
3619    protected void onAttachedToWindow() {
3620        super.onAttachedToWindow();
3621
3622        mTemporaryDetach = false;
3623
3624        if (mShowErrorAfterAttach) {
3625            showError();
3626            mShowErrorAfterAttach = false;
3627        }
3628    }
3629
3630    @Override
3631    protected void onDetachedFromWindow() {
3632        super.onDetachedFromWindow();
3633
3634        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
3635            final ViewTreeObserver observer = getViewTreeObserver();
3636            if (observer != null) {
3637                observer.removeOnPreDrawListener(this);
3638                mPreDrawState = PREDRAW_NOT_REGISTERED;
3639            }
3640        }
3641
3642        if (mError != null) {
3643            hideError();
3644        }
3645    }
3646
3647    @Override
3648    protected boolean isPaddingOffsetRequired() {
3649        return mShadowRadius != 0;
3650    }
3651
3652    @Override
3653    protected int getLeftPaddingOffset() {
3654        return (int) Math.min(0, mShadowDx - mShadowRadius);
3655    }
3656
3657    @Override
3658    protected int getTopPaddingOffset() {
3659        return (int) Math.min(0, mShadowDy - mShadowRadius);
3660    }
3661
3662    @Override
3663    protected int getBottomPaddingOffset() {
3664        return (int) Math.max(0, mShadowDy + mShadowRadius);
3665    }
3666
3667    @Override
3668    protected int getRightPaddingOffset() {
3669        return (int) Math.max(0, mShadowDx + mShadowRadius);
3670    }
3671
3672    @Override
3673    protected boolean verifyDrawable(Drawable who) {
3674        final boolean verified = super.verifyDrawable(who);
3675        if (!verified && mDrawables != null) {
3676            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
3677                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
3678        }
3679        return verified;
3680    }
3681
3682    @Override
3683    protected void onDraw(Canvas canvas) {
3684        restartMarqueeIfNeeded();
3685
3686        // Draw the background for this view
3687        super.onDraw(canvas);
3688
3689        final int compoundPaddingLeft = getCompoundPaddingLeft();
3690        final int compoundPaddingTop = getCompoundPaddingTop();
3691        final int compoundPaddingRight = getCompoundPaddingRight();
3692        final int compoundPaddingBottom = getCompoundPaddingBottom();
3693        final int scrollX = mScrollX;
3694        final int scrollY = mScrollY;
3695        final int right = mRight;
3696        final int left = mLeft;
3697        final int bottom = mBottom;
3698        final int top = mTop;
3699
3700        final Drawables dr = mDrawables;
3701        if (dr != null) {
3702            /*
3703             * Compound, not extended, because the icon is not clipped
3704             * if the text height is smaller.
3705             */
3706
3707            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
3708            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
3709
3710            if (dr.mDrawableLeft != null) {
3711                canvas.save();
3712                canvas.translate(scrollX + mPaddingLeft,
3713                                 scrollY + compoundPaddingTop +
3714                                 (vspace - dr.mDrawableHeightLeft) / 2);
3715                dr.mDrawableLeft.draw(canvas);
3716                canvas.restore();
3717            }
3718
3719            if (dr.mDrawableRight != null) {
3720                canvas.save();
3721                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
3722                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
3723                dr.mDrawableRight.draw(canvas);
3724                canvas.restore();
3725            }
3726
3727            if (dr.mDrawableTop != null) {
3728                canvas.save();
3729                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
3730                        scrollY + mPaddingTop);
3731                dr.mDrawableTop.draw(canvas);
3732                canvas.restore();
3733            }
3734
3735            if (dr.mDrawableBottom != null) {
3736                canvas.save();
3737                canvas.translate(scrollX + compoundPaddingLeft +
3738                        (hspace - dr.mDrawableWidthBottom) / 2,
3739                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
3740                dr.mDrawableBottom.draw(canvas);
3741                canvas.restore();
3742            }
3743        }
3744
3745        if (mPreDrawState == PREDRAW_DONE) {
3746            final ViewTreeObserver observer = getViewTreeObserver();
3747            if (observer != null) {
3748                observer.removeOnPreDrawListener(this);
3749                mPreDrawState = PREDRAW_NOT_REGISTERED;
3750            }
3751        }
3752
3753        int color = mCurTextColor;
3754
3755        if (mLayout == null) {
3756            assumeLayout();
3757        }
3758
3759        Layout layout = mLayout;
3760        int cursorcolor = color;
3761
3762        if (mHint != null && mText.length() == 0) {
3763            if (mHintTextColor != null) {
3764                color = mCurHintTextColor;
3765            }
3766
3767            layout = mHintLayout;
3768        }
3769
3770        mTextPaint.setColor(color);
3771        mTextPaint.drawableState = getDrawableState();
3772
3773        canvas.save();
3774        /*  Would be faster if we didn't have to do this. Can we chop the
3775            (displayable) text so that we don't need to do this ever?
3776        */
3777
3778        int extendedPaddingTop = getExtendedPaddingTop();
3779        int extendedPaddingBottom = getExtendedPaddingBottom();
3780
3781        float clipLeft = compoundPaddingLeft + scrollX;
3782        float clipTop = extendedPaddingTop + scrollY;
3783        float clipRight = right - left - compoundPaddingRight + scrollX;
3784        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
3785
3786        if (mShadowRadius != 0) {
3787            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
3788            clipRight += Math.max(0, mShadowDx + mShadowRadius);
3789
3790            clipTop += Math.min(0, mShadowDy - mShadowRadius);
3791            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
3792        }
3793
3794        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
3795
3796        int voffsetText = 0;
3797        int voffsetCursor = 0;
3798
3799        // translate in by our padding
3800        {
3801            /* shortcircuit calling getVerticaOffset() */
3802            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3803                voffsetText = getVerticalOffset(false);
3804                voffsetCursor = getVerticalOffset(true);
3805            }
3806            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
3807        }
3808
3809        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3810            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
3811                    (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
3812                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
3813                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
3814            }
3815
3816            if (mMarquee != null && mMarquee.isRunning()) {
3817                canvas.translate(-mMarquee.mScroll, 0.0f);
3818            }
3819        }
3820
3821        Path highlight = null;
3822        int selStart = -1, selEnd = -1;
3823
3824        //  If there is no movement method, then there can be no selection.
3825        //  Check that first and attempt to skip everything having to do with
3826        //  the cursor.
3827        //  XXX This is not strictly true -- a program could set the
3828        //  selection manually if it really wanted to.
3829        if (mMovement != null && (isFocused() || isPressed())) {
3830            selStart = Selection.getSelectionStart(mText);
3831            selEnd = Selection.getSelectionEnd(mText);
3832
3833            if (mCursorVisible && selStart >= 0 && isEnabled()) {
3834                if (mHighlightPath == null)
3835                    mHighlightPath = new Path();
3836
3837                if (selStart == selEnd) {
3838                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
3839                        < BLINK) {
3840                        if (mHighlightPathBogus) {
3841                            mHighlightPath.reset();
3842                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
3843                            mHighlightPathBogus = false;
3844                        }
3845
3846                        // XXX should pass to skin instead of drawing directly
3847                        mHighlightPaint.setColor(cursorcolor);
3848                        mHighlightPaint.setStyle(Paint.Style.STROKE);
3849
3850                        highlight = mHighlightPath;
3851                    }
3852                } else {
3853                    if (mHighlightPathBogus) {
3854                        mHighlightPath.reset();
3855                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
3856                        mHighlightPathBogus = false;
3857                    }
3858
3859                    // XXX should pass to skin instead of drawing directly
3860                    mHighlightPaint.setColor(mHighlightColor);
3861                    mHighlightPaint.setStyle(Paint.Style.FILL);
3862
3863                    highlight = mHighlightPath;
3864                }
3865            }
3866        }
3867
3868        /*  Comment out until we decide what to do about animations
3869        boolean isLinearTextOn = false;
3870        if (currentTransformation != null) {
3871            isLinearTextOn = mTextPaint.isLinearTextOn();
3872            Matrix m = currentTransformation.getMatrix();
3873            if (!m.isIdentity()) {
3874                // mTextPaint.setLinearTextOn(true);
3875            }
3876        }
3877        */
3878
3879        final InputMethodState ims = mInputMethodState;
3880        if (ims != null && ims.mBatchEditNesting == 0) {
3881            InputMethodManager imm = InputMethodManager.peekInstance();
3882            if (imm != null) {
3883                if (imm.isActive(this)) {
3884                    boolean reported = false;
3885                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
3886                        // We are in extract mode and the content has changed
3887                        // in some way... just report complete new text to the
3888                        // input method.
3889                        reported = reportExtractedText();
3890                    }
3891                    if (!reported && highlight != null) {
3892                        int candStart = -1;
3893                        int candEnd = -1;
3894                        if (mText instanceof Spannable) {
3895                            Spannable sp = (Spannable)mText;
3896                            candStart = EditableInputConnection.getComposingSpanStart(sp);
3897                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
3898                        }
3899                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
3900                    }
3901                }
3902
3903                if (imm.isWatchingCursor(this) && highlight != null) {
3904                    highlight.computeBounds(ims.mTmpRectF, true);
3905                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
3906
3907                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
3908                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
3909
3910                    ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
3911
3912                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
3913                            (int)(ims.mTmpRectF.top + 0.5),
3914                            (int)(ims.mTmpRectF.right + 0.5),
3915                            (int)(ims.mTmpRectF.bottom + 0.5));
3916
3917                    imm.updateCursor(this,
3918                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
3919                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
3920                }
3921            }
3922        }
3923
3924        layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
3925
3926        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
3927            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
3928            layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
3929        }
3930
3931        /*  Comment out until we decide what to do about animations
3932        if (currentTransformation != null) {
3933            mTextPaint.setLinearTextOn(isLinearTextOn);
3934        }
3935        */
3936
3937        canvas.restore();
3938    }
3939
3940    @Override
3941    public void getFocusedRect(Rect r) {
3942        if (mLayout == null) {
3943            super.getFocusedRect(r);
3944            return;
3945        }
3946
3947        int sel = getSelectionEnd();
3948        if (sel < 0) {
3949            super.getFocusedRect(r);
3950            return;
3951        }
3952
3953        int line = mLayout.getLineForOffset(sel);
3954        r.top = mLayout.getLineTop(line);
3955        r.bottom = mLayout.getLineBottom(line);
3956
3957        r.left = (int) mLayout.getPrimaryHorizontal(sel);
3958        r.right = r.left + 1;
3959
3960        // Adjust for padding and gravity.
3961        int paddingLeft = getCompoundPaddingLeft();
3962        int paddingTop = getExtendedPaddingTop();
3963        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3964            paddingTop += getVerticalOffset(false);
3965        }
3966        r.offset(paddingLeft, paddingTop);
3967    }
3968
3969    /**
3970     * Return the number of lines of text, or 0 if the internal Layout has not
3971     * been built.
3972     */
3973    public int getLineCount() {
3974        return mLayout != null ? mLayout.getLineCount() : 0;
3975    }
3976
3977    /**
3978     * Return the baseline for the specified line (0...getLineCount() - 1)
3979     * If bounds is not null, return the top, left, right, bottom extents
3980     * of the specified line in it. If the internal Layout has not been built,
3981     * return 0 and set bounds to (0, 0, 0, 0)
3982     * @param line which line to examine (0..getLineCount() - 1)
3983     * @param bounds Optional. If not null, it returns the extent of the line
3984     * @return the Y-coordinate of the baseline
3985     */
3986    public int getLineBounds(int line, Rect bounds) {
3987        if (mLayout == null) {
3988            if (bounds != null) {
3989                bounds.set(0, 0, 0, 0);
3990            }
3991            return 0;
3992        }
3993        else {
3994            int baseline = mLayout.getLineBounds(line, bounds);
3995
3996            int voffset = getExtendedPaddingTop();
3997            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3998                voffset += getVerticalOffset(true);
3999            }
4000            if (bounds != null) {
4001                bounds.offset(getCompoundPaddingLeft(), voffset);
4002            }
4003            return baseline + voffset;
4004        }
4005    }
4006
4007    @Override
4008    public int getBaseline() {
4009        if (mLayout == null) {
4010            return super.getBaseline();
4011        }
4012
4013        int voffset = 0;
4014        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4015            voffset = getVerticalOffset(true);
4016        }
4017
4018        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
4019    }
4020
4021    @Override
4022    public boolean onKeyDown(int keyCode, KeyEvent event) {
4023        int which = doKeyDown(keyCode, event, null);
4024        if (which == 0) {
4025            // Go through default dispatching.
4026            return super.onKeyDown(keyCode, event);
4027        }
4028
4029        return true;
4030    }
4031
4032    @Override
4033    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4034        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
4035
4036        int which = doKeyDown(keyCode, down, event);
4037        if (which == 0) {
4038            // Go through default dispatching.
4039            return super.onKeyMultiple(keyCode, repeatCount, event);
4040        }
4041        if (which == -1) {
4042            // Consumed the whole thing.
4043            return true;
4044        }
4045
4046        repeatCount--;
4047
4048        // We are going to dispatch the remaining events to either the input
4049        // or movement method.  To do this, we will just send a repeated stream
4050        // of down and up events until we have done the complete repeatCount.
4051        // It would be nice if those interfaces had an onKeyMultiple() method,
4052        // but adding that is a more complicated change.
4053        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
4054        if (which == 1) {
4055            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4056            while (--repeatCount > 0) {
4057                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
4058                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4059            }
4060            if (mError != null && !mErrorWasChanged) {
4061                setError(null, null);
4062            }
4063
4064        } else if (which == 2) {
4065            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4066            while (--repeatCount > 0) {
4067                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
4068                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4069            }
4070        }
4071
4072        return true;
4073    }
4074
4075    /**
4076     * Returns true if pressing ENTER in this field advances focus instead
4077     * of inserting the character.  This is true mostly in single-line fields,
4078     * but also in mail addresses and subjects which will display on multiple
4079     * lines but where it doesn't make sense to insert newlines.
4080     */
4081    private boolean shouldAdvanceFocusOnEnter() {
4082        if (mInput == null) {
4083            return false;
4084        }
4085
4086        if (mSingleLine) {
4087            return true;
4088        }
4089
4090        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4091            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4092
4093            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
4094                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
4095                return true;
4096            }
4097        }
4098
4099        return false;
4100    }
4101
4102    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
4103        if (!isEnabled()) {
4104            return 0;
4105        }
4106
4107        switch (keyCode) {
4108            case KeyEvent.KEYCODE_ENTER:
4109                // If ALT modifier is held, then we always insert a
4110                // newline character.
4111                if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
4112
4113                    // When mInputContentType is set, we know that we are
4114                    // running in a "modern" cupcake environment, so don't need
4115                    // to worry about the application trying to capture
4116                    // enter key events.
4117                    if (mInputContentType != null) {
4118
4119                        // If there is an action listener, given them a
4120                        // chance to consume the event.
4121                        if (mInputContentType.onEditorActionListener != null &&
4122                                mInputContentType.onEditorActionListener.onEditorAction(
4123                                this, EditorInfo.IME_NULL, event)) {
4124                            mInputContentType.enterDown = true;
4125                            // We are consuming the enter key for them.
4126                            return -1;
4127                        }
4128                    }
4129
4130                    // If our editor should move focus when enter is pressed, or
4131                    // this is a generated event from an IME action button, then
4132                    // don't let it be inserted into the text.
4133                    if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4134                            || shouldAdvanceFocusOnEnter()) {
4135                        return -1;
4136                    }
4137                }
4138                break;
4139
4140            case KeyEvent.KEYCODE_DPAD_CENTER:
4141                if (shouldAdvanceFocusOnEnter()) {
4142                    return 0;
4143                }
4144        }
4145
4146        if (mInput != null) {
4147            /*
4148             * Keep track of what the error was before doing the input
4149             * so that if an input filter changed the error, we leave
4150             * that error showing.  Otherwise, we take down whatever
4151             * error was showing when the user types something.
4152             */
4153            mErrorWasChanged = false;
4154
4155            boolean doDown = true;
4156            if (otherEvent != null) {
4157                try {
4158                    beginBatchEdit();
4159                    boolean handled = mInput.onKeyOther(this, (Editable) mText,
4160                            otherEvent);
4161                    if (mError != null && !mErrorWasChanged) {
4162                        setError(null, null);
4163                    }
4164                    doDown = false;
4165                    if (handled) {
4166                        return -1;
4167                    }
4168                } catch (AbstractMethodError e) {
4169                    // onKeyOther was added after 1.0, so if it isn't
4170                    // implemented we need to try to dispatch as a regular down.
4171                } finally {
4172                    endBatchEdit();
4173                }
4174            }
4175
4176            if (doDown) {
4177                beginBatchEdit();
4178                if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
4179                    endBatchEdit();
4180                    if (mError != null && !mErrorWasChanged) {
4181                        setError(null, null);
4182                    }
4183                    return 1;
4184                }
4185                endBatchEdit();
4186            }
4187        }
4188
4189        // bug 650865: sometimes we get a key event before a layout.
4190        // don't try to move around if we don't know the layout.
4191
4192        if (mMovement != null && mLayout != null) {
4193            boolean doDown = true;
4194            if (otherEvent != null) {
4195                try {
4196                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
4197                            otherEvent);
4198                    doDown = false;
4199                    if (handled) {
4200                        return -1;
4201                    }
4202                } catch (AbstractMethodError e) {
4203                    // onKeyOther was added after 1.0, so if it isn't
4204                    // implemented we need to try to dispatch as a regular down.
4205                }
4206            }
4207            if (doDown) {
4208                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
4209                    return 2;
4210            }
4211        }
4212
4213        return 0;
4214    }
4215
4216    @Override
4217    public boolean onKeyUp(int keyCode, KeyEvent event) {
4218        if (!isEnabled()) {
4219            return super.onKeyUp(keyCode, event);
4220        }
4221
4222        switch (keyCode) {
4223            case KeyEvent.KEYCODE_DPAD_CENTER:
4224                /*
4225                 * If there is a click listener, just call through to
4226                 * super, which will invoke it.
4227                 *
4228                 * If there isn't a click listener, try to show the soft
4229                 * input method.  (It will also
4230                 * call performClick(), but that won't do anything in
4231                 * this case.)
4232                 */
4233                if (mOnClickListener == null) {
4234                    if (mMovement != null && mText instanceof Editable
4235                            && mLayout != null && onCheckIsTextEditor()) {
4236                        InputMethodManager imm = (InputMethodManager)
4237                                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
4238                        imm.showSoftInput(this, 0);
4239                    }
4240                }
4241                return super.onKeyUp(keyCode, event);
4242
4243            case KeyEvent.KEYCODE_ENTER:
4244                if (mInputContentType != null
4245                        && mInputContentType.onEditorActionListener != null
4246                        && mInputContentType.enterDown) {
4247                    mInputContentType.enterDown = false;
4248                    if (mInputContentType.onEditorActionListener.onEditorAction(
4249                            this, EditorInfo.IME_NULL, event)) {
4250                        return true;
4251                    }
4252                }
4253
4254                if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4255                        || shouldAdvanceFocusOnEnter()) {
4256                    /*
4257                     * If there is a click listener, just call through to
4258                     * super, which will invoke it.
4259                     *
4260                     * If there isn't a click listener, try to advance focus,
4261                     * but still call through to super, which will reset the
4262                     * pressed state and longpress state.  (It will also
4263                     * call performClick(), but that won't do anything in
4264                     * this case.)
4265                     */
4266                    if (mOnClickListener == null) {
4267                        View v = focusSearch(FOCUS_DOWN);
4268
4269                        if (v != null) {
4270                            if (!v.requestFocus(FOCUS_DOWN)) {
4271                                throw new IllegalStateException("focus search returned a view " +
4272                                        "that wasn't able to take focus!");
4273                            }
4274
4275                            /*
4276                             * Return true because we handled the key; super
4277                             * will return false because there was no click
4278                             * listener.
4279                             */
4280                            super.onKeyUp(keyCode, event);
4281                            return true;
4282                        } else if ((event.getFlags()
4283                                & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4284                            // No target for next focus, but make sure the IME
4285                            // if this came from it.
4286                            InputMethodManager imm = InputMethodManager.peekInstance();
4287                            if (imm != null) {
4288                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
4289                            }
4290                        }
4291                    }
4292
4293                    return super.onKeyUp(keyCode, event);
4294                }
4295        }
4296
4297        if (mInput != null)
4298            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
4299                return true;
4300
4301        if (mMovement != null && mLayout != null)
4302            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
4303                return true;
4304
4305        return super.onKeyUp(keyCode, event);
4306    }
4307
4308    @Override public boolean onCheckIsTextEditor() {
4309        return mInputType != EditorInfo.TYPE_NULL;
4310    }
4311
4312    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4313        if (onCheckIsTextEditor()) {
4314            if (mInputMethodState == null) {
4315                mInputMethodState = new InputMethodState();
4316            }
4317            outAttrs.inputType = mInputType;
4318            if (mInputContentType != null) {
4319                outAttrs.imeOptions = mInputContentType.imeOptions;
4320                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
4321                outAttrs.actionLabel = mInputContentType.imeActionLabel;
4322                outAttrs.actionId = mInputContentType.imeActionId;
4323                outAttrs.extras = mInputContentType.extras;
4324            } else {
4325                outAttrs.imeOptions = EditorInfo.IME_NULL;
4326            }
4327            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
4328                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
4329                if (focusSearch(FOCUS_DOWN) != null) {
4330                    // An action has not been set, but the enter key will move to
4331                    // the next focus, so set the action to that.
4332                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
4333                } else {
4334                    // An action has not been set, and there is no focus to move
4335                    // to, so let's just supply a "done" action.
4336                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
4337                }
4338                if (!shouldAdvanceFocusOnEnter()) {
4339                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4340                }
4341            }
4342            if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS
4343                    | InputType.TYPE_TEXT_FLAG_MULTI_LINE))
4344                    == (InputType.TYPE_CLASS_TEXT
4345                            | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) {
4346                // Multi-line text editors should always show an enter key.
4347                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4348            }
4349            outAttrs.hintText = mHint;
4350            if (mText instanceof Editable) {
4351                InputConnection ic = new EditableInputConnection(this);
4352                outAttrs.initialSelStart = Selection.getSelectionStart(mText);
4353                outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
4354                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
4355                return ic;
4356            }
4357        }
4358        return null;
4359    }
4360
4361    /**
4362     * If this TextView contains editable content, extract a portion of it
4363     * based on the information in <var>request</var> in to <var>outText</var>.
4364     * @return Returns true if the text was successfully extracted, else false.
4365     */
4366    public boolean extractText(ExtractedTextRequest request,
4367            ExtractedText outText) {
4368        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
4369                EXTRACT_UNKNOWN, outText);
4370    }
4371
4372    static final int EXTRACT_NOTHING = -2;
4373    static final int EXTRACT_UNKNOWN = -1;
4374
4375    boolean extractTextInternal(ExtractedTextRequest request,
4376            int partialStartOffset, int partialEndOffset, int delta,
4377            ExtractedText outText) {
4378        final CharSequence content = mText;
4379        if (content != null) {
4380            if (partialStartOffset != EXTRACT_NOTHING) {
4381                final int N = content.length();
4382                if (partialStartOffset < 0) {
4383                    outText.partialStartOffset = outText.partialEndOffset = -1;
4384                    partialStartOffset = 0;
4385                    partialEndOffset = N;
4386                } else {
4387                    // Adjust offsets to ensure we contain full spans.
4388                    if (content instanceof Spanned) {
4389                        Spanned spanned = (Spanned)content;
4390                        Object[] spans = spanned.getSpans(partialStartOffset,
4391                                partialEndOffset, ParcelableSpan.class);
4392                        int i = spans.length;
4393                        while (i > 0) {
4394                            i--;
4395                            int j = spanned.getSpanStart(spans[i]);
4396                            if (j < partialStartOffset) partialStartOffset = j;
4397                            j = spanned.getSpanEnd(spans[i]);
4398                            if (j > partialEndOffset) partialEndOffset = j;
4399                        }
4400                    }
4401                    outText.partialStartOffset = partialStartOffset;
4402                    outText.partialEndOffset = partialEndOffset;
4403                    // Now use the delta to determine the actual amount of text
4404                    // we need.
4405                    partialEndOffset += delta;
4406                    if (partialEndOffset > N) {
4407                        partialEndOffset = N;
4408                    } else if (partialEndOffset < 0) {
4409                        partialEndOffset = 0;
4410                    }
4411                }
4412                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
4413                    outText.text = content.subSequence(partialStartOffset,
4414                            partialEndOffset);
4415                } else {
4416                    outText.text = TextUtils.substring(content, partialStartOffset,
4417                            partialEndOffset);
4418                }
4419            }
4420            outText.flags = 0;
4421            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
4422                outText.flags |= ExtractedText.FLAG_SELECTING;
4423            }
4424            if (mSingleLine) {
4425                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
4426            }
4427            outText.startOffset = 0;
4428            outText.selectionStart = Selection.getSelectionStart(content);
4429            outText.selectionEnd = Selection.getSelectionEnd(content);
4430            return true;
4431        }
4432        return false;
4433    }
4434
4435    boolean reportExtractedText() {
4436        final InputMethodState ims = mInputMethodState;
4437        if (ims != null) {
4438            final boolean contentChanged = ims.mContentChanged;
4439            if (contentChanged || ims.mSelectionModeChanged) {
4440                ims.mContentChanged = false;
4441                ims.mSelectionModeChanged = false;
4442                final ExtractedTextRequest req = mInputMethodState.mExtracting;
4443                if (req != null) {
4444                    InputMethodManager imm = InputMethodManager.peekInstance();
4445                    if (imm != null) {
4446                        if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
4447                                + ims.mChangedStart + " end=" + ims.mChangedEnd
4448                                + " delta=" + ims.mChangedDelta);
4449                        if (ims.mChangedStart < 0 && !contentChanged) {
4450                            ims.mChangedStart = EXTRACT_NOTHING;
4451                        }
4452                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
4453                                ims.mChangedDelta, ims.mTmpExtracted)) {
4454                            if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
4455                                    + ims.mTmpExtracted.partialStartOffset
4456                                    + " end=" + ims.mTmpExtracted.partialEndOffset
4457                                    + ": " + ims.mTmpExtracted.text);
4458                            imm.updateExtractedText(this, req.token,
4459                                    mInputMethodState.mTmpExtracted);
4460                            return true;
4461                        }
4462                    }
4463                }
4464            }
4465        }
4466        return false;
4467    }
4468
4469    /**
4470     * This is used to remove all style-impacting spans from text before new
4471     * extracted text is being replaced into it, so that we don't have any
4472     * lingering spans applied during the replace.
4473     */
4474    static void removeParcelableSpans(Spannable spannable, int start, int end) {
4475        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
4476        int i = spans.length;
4477        while (i > 0) {
4478            i--;
4479            spannable.removeSpan(spans[i]);
4480        }
4481    }
4482
4483    /**
4484     * Apply to this text view the given extracted text, as previously
4485     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
4486     */
4487    public void setExtractedText(ExtractedText text) {
4488        Editable content = getEditableText();
4489        if (text.text != null) {
4490            if (content == null) {
4491                setText(text.text, TextView.BufferType.EDITABLE);
4492            } else if (text.partialStartOffset < 0) {
4493                removeParcelableSpans(content, 0, content.length());
4494                content.replace(0, content.length(), text.text);
4495            } else {
4496                final int N = content.length();
4497                int start = text.partialStartOffset;
4498                if (start > N) start = N;
4499                int end = text.partialEndOffset;
4500                if (end > N) end = N;
4501                removeParcelableSpans(content, start, end);
4502                content.replace(start, end, text.text);
4503            }
4504        }
4505
4506        // Now set the selection position...  make sure it is in range, to
4507        // avoid crashes.  If this is a partial update, it is possible that
4508        // the underlying text may have changed, causing us problems here.
4509        // Also we just don't want to trust clients to do the right thing.
4510        Spannable sp = (Spannable)getText();
4511        final int N = sp.length();
4512        int start = text.selectionStart;
4513        if (start < 0) start = 0;
4514        else if (start > N) start = N;
4515        int end = text.selectionEnd;
4516        if (end < 0) end = 0;
4517        else if (end > N) end = N;
4518        Selection.setSelection(sp, start, end);
4519
4520        // Finally, update the selection mode.
4521        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
4522            MetaKeyKeyListener.startSelecting(this, sp);
4523        } else {
4524            MetaKeyKeyListener.stopSelecting(this, sp);
4525        }
4526    }
4527
4528    /**
4529     * @hide
4530     */
4531    public void setExtracting(ExtractedTextRequest req) {
4532        if (mInputMethodState != null) {
4533            mInputMethodState.mExtracting = req;
4534        }
4535    }
4536
4537    /**
4538     * Called by the framework in response to a text completion from
4539     * the current input method, provided by it calling
4540     * {@link InputConnection#commitCompletion
4541     * InputConnection.commitCompletion()}.  The default implementation does
4542     * nothing; text views that are supporting auto-completion should override
4543     * this to do their desired behavior.
4544     *
4545     * @param text The auto complete text the user has selected.
4546     */
4547    public void onCommitCompletion(CompletionInfo text) {
4548    }
4549
4550    public void beginBatchEdit() {
4551        final InputMethodState ims = mInputMethodState;
4552        if (ims != null) {
4553            int nesting = ++ims.mBatchEditNesting;
4554            if (nesting == 1) {
4555                ims.mCursorChanged = false;
4556                ims.mChangedDelta = 0;
4557                if (ims.mContentChanged) {
4558                    // We already have a pending change from somewhere else,
4559                    // so turn this into a full update.
4560                    ims.mChangedStart = 0;
4561                    ims.mChangedEnd = mText.length();
4562                } else {
4563                    ims.mChangedStart = EXTRACT_UNKNOWN;
4564                    ims.mChangedEnd = EXTRACT_UNKNOWN;
4565                    ims.mContentChanged = false;
4566                }
4567                onBeginBatchEdit();
4568            }
4569        }
4570    }
4571
4572    public void endBatchEdit() {
4573        final InputMethodState ims = mInputMethodState;
4574        if (ims != null) {
4575            int nesting = --ims.mBatchEditNesting;
4576            if (nesting == 0) {
4577                finishBatchEdit(ims);
4578            }
4579        }
4580    }
4581
4582    void ensureEndedBatchEdit() {
4583        final InputMethodState ims = mInputMethodState;
4584        if (ims != null && ims.mBatchEditNesting != 0) {
4585            ims.mBatchEditNesting = 0;
4586            finishBatchEdit(ims);
4587        }
4588    }
4589
4590    void finishBatchEdit(final InputMethodState ims) {
4591        onEndBatchEdit();
4592
4593        if (ims.mContentChanged || ims.mSelectionModeChanged) {
4594            updateAfterEdit();
4595            reportExtractedText();
4596        } else if (ims.mCursorChanged) {
4597            // Cheezy way to get us to report the current cursor location.
4598            invalidateCursor();
4599        }
4600    }
4601
4602    void updateAfterEdit() {
4603        invalidate();
4604        int curs = Selection.getSelectionStart(mText);
4605
4606        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4607                             Gravity.BOTTOM) {
4608            registerForPreDraw();
4609        }
4610
4611        if (curs >= 0) {
4612            mHighlightPathBogus = true;
4613
4614            if (isFocused()) {
4615                mShowCursor = SystemClock.uptimeMillis();
4616                makeBlink();
4617            }
4618        }
4619
4620        checkForResize();
4621    }
4622
4623    /**
4624     * Called by the framework in response to a request to begin a batch
4625     * of edit operations through a call to link {@link #beginBatchEdit()}.
4626     */
4627    public void onBeginBatchEdit() {
4628    }
4629
4630    /**
4631     * Called by the framework in response to a request to end a batch
4632     * of edit operations through a call to link {@link #endBatchEdit}.
4633     */
4634    public void onEndBatchEdit() {
4635    }
4636
4637    /**
4638     * Called by the framework in response to a private command from the
4639     * current method, provided by it calling
4640     * {@link InputConnection#performPrivateCommand
4641     * InputConnection.performPrivateCommand()}.
4642     *
4643     * @param action The action name of the command.
4644     * @param data Any additional data for the command.  This may be null.
4645     * @return Return true if you handled the command, else false.
4646     */
4647    public boolean onPrivateIMECommand(String action, Bundle data) {
4648        return false;
4649    }
4650
4651    private void nullLayouts() {
4652        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
4653            mSavedLayout = (BoringLayout) mLayout;
4654        }
4655        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
4656            mSavedHintLayout = (BoringLayout) mHintLayout;
4657        }
4658
4659        mLayout = mHintLayout = null;
4660    }
4661
4662    /**
4663     * Make a new Layout based on the already-measured size of the view,
4664     * on the assumption that it was measured correctly at some point.
4665     */
4666    private void assumeLayout() {
4667        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4668
4669        if (width < 1) {
4670            width = 0;
4671        }
4672
4673        int physicalWidth = width;
4674
4675        if (mHorizontallyScrolling) {
4676            width = VERY_WIDE;
4677        }
4678
4679        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
4680                      physicalWidth, false);
4681    }
4682
4683    /**
4684     * The width passed in is now the desired layout width,
4685     * not the full view width with padding.
4686     * {@hide}
4687     */
4688    protected void makeNewLayout(int w, int hintWidth,
4689                                 BoringLayout.Metrics boring,
4690                                 BoringLayout.Metrics hintBoring,
4691                                 int ellipsisWidth, boolean bringIntoView) {
4692        stopMarquee();
4693
4694        mHighlightPathBogus = true;
4695
4696        if (w < 0) {
4697            w = 0;
4698        }
4699        if (hintWidth < 0) {
4700            hintWidth = 0;
4701        }
4702
4703        Layout.Alignment alignment;
4704        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
4705            case Gravity.CENTER_HORIZONTAL:
4706                alignment = Layout.Alignment.ALIGN_CENTER;
4707                break;
4708
4709            case Gravity.RIGHT:
4710                alignment = Layout.Alignment.ALIGN_OPPOSITE;
4711                break;
4712
4713            default:
4714                alignment = Layout.Alignment.ALIGN_NORMAL;
4715        }
4716
4717        if (mText instanceof Spannable) {
4718            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
4719                    alignment, mSpacingMult,
4720                    mSpacingAdd, mIncludePad, mEllipsize,
4721                    ellipsisWidth);
4722        } else {
4723            if (boring == UNKNOWN_BORING) {
4724                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4725                                               mBoring);
4726                if (boring != null) {
4727                    mBoring = boring;
4728                }
4729            }
4730
4731            if (boring != null) {
4732                if (boring.width <= w &&
4733                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
4734                    if (mSavedLayout != null) {
4735                        mLayout = mSavedLayout.
4736                                replaceOrMake(mTransformed, mTextPaint,
4737                                w, alignment, mSpacingMult, mSpacingAdd,
4738                                boring, mIncludePad);
4739                    } else {
4740                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4741                                w, alignment, mSpacingMult, mSpacingAdd,
4742                                boring, mIncludePad);
4743                    }
4744                    // Log.e("aaa", "Boring: " + mTransformed);
4745
4746                    mSavedLayout = (BoringLayout) mLayout;
4747                } else if (mEllipsize != null && boring.width <= w) {
4748                    if (mSavedLayout != null) {
4749                        mLayout = mSavedLayout.
4750                                replaceOrMake(mTransformed, mTextPaint,
4751                                w, alignment, mSpacingMult, mSpacingAdd,
4752                                boring, mIncludePad, mEllipsize,
4753                                ellipsisWidth);
4754                    } else {
4755                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4756                                w, alignment, mSpacingMult, mSpacingAdd,
4757                                boring, mIncludePad, mEllipsize,
4758                                ellipsisWidth);
4759                    }
4760                } else if (mEllipsize != null) {
4761                    mLayout = new StaticLayout(mTransformed,
4762                                0, mTransformed.length(),
4763                                mTextPaint, w, alignment, mSpacingMult,
4764                                mSpacingAdd, mIncludePad, mEllipsize,
4765                                ellipsisWidth);
4766                } else {
4767                    mLayout = new StaticLayout(mTransformed, mTextPaint,
4768                            w, alignment, mSpacingMult, mSpacingAdd,
4769                            mIncludePad);
4770                    // Log.e("aaa", "Boring but wide: " + mTransformed);
4771                }
4772            } else if (mEllipsize != null) {
4773                mLayout = new StaticLayout(mTransformed,
4774                            0, mTransformed.length(),
4775                            mTextPaint, w, alignment, mSpacingMult,
4776                            mSpacingAdd, mIncludePad, mEllipsize,
4777                            ellipsisWidth);
4778            } else {
4779                mLayout = new StaticLayout(mTransformed, mTextPaint,
4780                        w, alignment, mSpacingMult, mSpacingAdd,
4781                        mIncludePad);
4782            }
4783        }
4784
4785        mHintLayout = null;
4786
4787        if (mHint != null) {
4788            if (hintBoring == UNKNOWN_BORING) {
4789                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
4790                                                   mHintBoring);
4791                if (hintBoring != null) {
4792                    mHintBoring = hintBoring;
4793                }
4794            }
4795
4796            if (hintBoring != null) {
4797                if (hintBoring.width <= hintWidth) {
4798                    if (mSavedHintLayout != null) {
4799                        mHintLayout = mSavedHintLayout.
4800                                replaceOrMake(mHint, mTextPaint,
4801                                hintWidth, alignment, mSpacingMult,
4802                                mSpacingAdd, hintBoring, mIncludePad);
4803                    } else {
4804                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
4805                                hintWidth, alignment, mSpacingMult,
4806                                mSpacingAdd, hintBoring, mIncludePad);
4807                    }
4808
4809                    mSavedHintLayout = (BoringLayout) mHintLayout;
4810                } else {
4811                    mHintLayout = new StaticLayout(mHint, mTextPaint,
4812                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
4813                            mIncludePad);
4814                }
4815            } else {
4816                mHintLayout = new StaticLayout(mHint, mTextPaint,
4817                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
4818                        mIncludePad);
4819            }
4820        }
4821
4822        if (bringIntoView) {
4823            registerForPreDraw();
4824        }
4825
4826        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4827            if (!compressText(ellipsisWidth)) {
4828                final int height = mLayoutParams.height;
4829                // If the size of the view does not depend on the size of the text, try to
4830                // start the marquee immediately
4831                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
4832                    startMarquee();
4833                } else {
4834                    // Defer the start of the marquee until we know our width (see setFrame())
4835                    mRestartMarquee = true;
4836                }
4837            }
4838        }
4839    }
4840
4841    private boolean compressText(float width) {
4842        // Only compress the text if it hasn't been compressed by the previous pass
4843        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
4844                mTextPaint.getTextScaleX() == 1.0f) {
4845            final float textWidth = mLayout.getLineWidth(0);
4846            final float overflow = (textWidth + 1.0f - width) / width;
4847            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
4848                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
4849                post(new Runnable() {
4850                    public void run() {
4851                        requestLayout();
4852                    }
4853                });
4854                return true;
4855            }
4856        }
4857
4858        return false;
4859    }
4860
4861    private static int desired(Layout layout) {
4862        int n = layout.getLineCount();
4863        CharSequence text = layout.getText();
4864        float max = 0;
4865
4866        // if any line was wrapped, we can't use it.
4867        // but it's ok for the last line not to have a newline
4868
4869        for (int i = 0; i < n - 1; i++) {
4870            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
4871                return -1;
4872        }
4873
4874        for (int i = 0; i < n; i++) {
4875            max = Math.max(max, layout.getLineWidth(i));
4876        }
4877
4878        return (int) FloatMath.ceil(max);
4879    }
4880
4881    /**
4882     * Set whether the TextView includes extra top and bottom padding to make
4883     * room for accents that go above the normal ascent and descent.
4884     * The default is true.
4885     *
4886     * @attr ref android.R.styleable#TextView_includeFontPadding
4887     */
4888    public void setIncludeFontPadding(boolean includepad) {
4889        mIncludePad = includepad;
4890
4891        if (mLayout != null) {
4892            nullLayouts();
4893            requestLayout();
4894            invalidate();
4895        }
4896    }
4897
4898    private static final BoringLayout.Metrics UNKNOWN_BORING =
4899                                                new BoringLayout.Metrics();
4900
4901    @Override
4902    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
4903        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
4904        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
4905        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
4906        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
4907
4908        int width;
4909        int height;
4910
4911        BoringLayout.Metrics boring = UNKNOWN_BORING;
4912        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
4913
4914        int des = -1;
4915        boolean fromexisting = false;
4916
4917        if (widthMode == MeasureSpec.EXACTLY) {
4918            // Parent has told us how big to be. So be it.
4919            width = widthSize;
4920        } else {
4921            if (mLayout != null && mEllipsize == null) {
4922                des = desired(mLayout);
4923            }
4924
4925            if (des < 0) {
4926                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4927                                               mBoring);
4928                if (boring != null) {
4929                    mBoring = boring;
4930                }
4931            } else {
4932                fromexisting = true;
4933            }
4934
4935            if (boring == null || boring == UNKNOWN_BORING) {
4936                if (des < 0) {
4937                    des = (int) FloatMath.ceil(Layout.
4938                                    getDesiredWidth(mTransformed, mTextPaint));
4939                }
4940
4941                width = des;
4942            } else {
4943                width = boring.width;
4944            }
4945
4946            final Drawables dr = mDrawables;
4947            if (dr != null) {
4948                width = Math.max(width, dr.mDrawableWidthTop);
4949                width = Math.max(width, dr.mDrawableWidthBottom);
4950            }
4951
4952            if (mHint != null) {
4953                int hintDes = -1;
4954                int hintWidth;
4955
4956                if (mHintLayout != null) {
4957                    hintDes = desired(mHintLayout);
4958                }
4959
4960                if (hintDes < 0) {
4961                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
4962                                                       mHintBoring);
4963                    if (hintBoring != null) {
4964                        mHintBoring = hintBoring;
4965                    }
4966                }
4967
4968                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
4969                    if (hintDes < 0) {
4970                        hintDes = (int) FloatMath.ceil(Layout.
4971                                        getDesiredWidth(mHint, mTextPaint));
4972                    }
4973
4974                    hintWidth = hintDes;
4975                } else {
4976                    hintWidth = hintBoring.width;
4977                }
4978
4979                if (hintWidth > width) {
4980                    width = hintWidth;
4981                }
4982            }
4983
4984            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
4985
4986            if (mMaxWidthMode == EMS) {
4987                width = Math.min(width, mMaxWidth * getLineHeight());
4988            } else {
4989                width = Math.min(width, mMaxWidth);
4990            }
4991
4992            if (mMinWidthMode == EMS) {
4993                width = Math.max(width, mMinWidth * getLineHeight());
4994            } else {
4995                width = Math.max(width, mMinWidth);
4996            }
4997
4998            // Check against our minimum width
4999            width = Math.max(width, getSuggestedMinimumWidth());
5000
5001            if (widthMode == MeasureSpec.AT_MOST) {
5002                width = Math.min(widthSize, width);
5003            }
5004        }
5005
5006        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5007        int unpaddedWidth = want;
5008        int hintWant = want;
5009
5010        if (mHorizontallyScrolling)
5011            want = VERY_WIDE;
5012
5013        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5014
5015        if (mLayout == null) {
5016            makeNewLayout(want, hintWant, boring, hintBoring,
5017                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
5018                          false);
5019        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5020                   (mLayout.getEllipsizedWidth() !=
5021                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5022            if (mHint == null && mEllipsize == null &&
5023                    want > mLayout.getWidth() &&
5024                    (mLayout instanceof BoringLayout ||
5025                        (fromexisting && des >= 0 && des <= want))) {
5026                mLayout.increaseWidthTo(want);
5027            } else {
5028                makeNewLayout(want, hintWant, boring, hintBoring,
5029                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
5030                              false);
5031            }
5032        } else {
5033            // Width has not changed.
5034        }
5035
5036        if (heightMode == MeasureSpec.EXACTLY) {
5037            // Parent has told us how big to be. So be it.
5038            height = heightSize;
5039            mDesiredHeightAtMeasure = -1;
5040        } else {
5041            int desired = getDesiredHeight();
5042
5043            height = desired;
5044            mDesiredHeightAtMeasure = desired;
5045
5046            if (heightMode == MeasureSpec.AT_MOST) {
5047                height = Math.min(desired, height);
5048            }
5049        }
5050
5051        int unpaddedHeight = height - getCompoundPaddingTop() -
5052                                getCompoundPaddingBottom();
5053        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
5054            unpaddedHeight = Math.min(unpaddedHeight,
5055                                      mLayout.getLineTop(mMaximum));
5056        }
5057
5058        /*
5059         * We didn't let makeNewLayout() register to bring the cursor into view,
5060         * so do it here if there is any possibility that it is needed.
5061         */
5062        if (mMovement != null ||
5063            mLayout.getWidth() > unpaddedWidth ||
5064            mLayout.getHeight() > unpaddedHeight) {
5065            registerForPreDraw();
5066        } else {
5067            scrollTo(0, 0);
5068        }
5069
5070        setMeasuredDimension(width, height);
5071    }
5072
5073    private int getDesiredHeight() {
5074        return Math.max(getDesiredHeight(mLayout, true),
5075                        getDesiredHeight(mHintLayout, false));
5076    }
5077
5078    private int getDesiredHeight(Layout layout, boolean cap) {
5079        if (layout == null) {
5080            return 0;
5081        }
5082
5083        int linecount = layout.getLineCount();
5084        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5085        int desired = layout.getLineTop(linecount);
5086
5087        final Drawables dr = mDrawables;
5088        if (dr != null) {
5089            desired = Math.max(desired, dr.mDrawableHeightLeft);
5090            desired = Math.max(desired, dr.mDrawableHeightRight);
5091        }
5092
5093        desired += pad;
5094
5095        if (mMaxMode == LINES) {
5096            /*
5097             * Don't cap the hint to a certain number of lines.
5098             * (Do cap it, though, if we have a maximum pixel height.)
5099             */
5100            if (cap) {
5101                if (linecount > mMaximum) {
5102                    desired = layout.getLineTop(mMaximum) +
5103                              layout.getBottomPadding();
5104
5105                    if (dr != null) {
5106                        desired = Math.max(desired, dr.mDrawableHeightLeft);
5107                        desired = Math.max(desired, dr.mDrawableHeightRight);
5108                    }
5109
5110                    desired += pad;
5111                    linecount = mMaximum;
5112                }
5113            }
5114        } else {
5115            desired = Math.min(desired, mMaximum);
5116        }
5117
5118        if (mMinMode == LINES) {
5119            if (linecount < mMinimum) {
5120                desired += getLineHeight() * (mMinimum - linecount);
5121            }
5122        } else {
5123            desired = Math.max(desired, mMinimum);
5124        }
5125
5126        // Check against our minimum height
5127        desired = Math.max(desired, getSuggestedMinimumHeight());
5128
5129        return desired;
5130    }
5131
5132    /**
5133     * Check whether a change to the existing text layout requires a
5134     * new view layout.
5135     */
5136    private void checkForResize() {
5137        boolean sizeChanged = false;
5138
5139        if (mLayout != null) {
5140            // Check if our width changed
5141            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
5142                sizeChanged = true;
5143                invalidate();
5144            }
5145
5146            // Check if our height changed
5147            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
5148                int desiredHeight = getDesiredHeight();
5149
5150                if (desiredHeight != this.getHeight()) {
5151                    sizeChanged = true;
5152                }
5153            } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
5154                if (mDesiredHeightAtMeasure >= 0) {
5155                    int desiredHeight = getDesiredHeight();
5156
5157                    if (desiredHeight != mDesiredHeightAtMeasure) {
5158                        sizeChanged = true;
5159                    }
5160                }
5161            }
5162        }
5163
5164        if (sizeChanged) {
5165            requestLayout();
5166            // caller will have already invalidated
5167        }
5168    }
5169
5170    /**
5171     * Check whether entirely new text requires a new view layout
5172     * or merely a new text layout.
5173     */
5174    private void checkForRelayout() {
5175        // If we have a fixed width, we can just swap in a new text layout
5176        // if the text height stays the same or if the view height is fixed.
5177
5178        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
5179                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
5180                (mHint == null || mHintLayout != null) &&
5181                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
5182            // Static width, so try making a new text layout.
5183
5184            int oldht = mLayout.getHeight();
5185            int want = mLayout.getWidth();
5186            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5187
5188            /*
5189             * No need to bring the text into view, since the size is not
5190             * changing (unless we do the requestLayout(), in which case it
5191             * will happen at measure).
5192             */
5193            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5194                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5195
5196            // In a fixed-height view, so use our new text layout.
5197            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
5198                mLayoutParams.height != LayoutParams.FILL_PARENT) {
5199                invalidate();
5200                return;
5201            }
5202
5203            // Dynamic height, but height has stayed the same,
5204            // so use our new text layout.
5205            if (mLayout.getHeight() == oldht &&
5206                (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
5207                invalidate();
5208                return;
5209            }
5210
5211            // We lose: the height has changed and we have a dynamic height.
5212            // Request a new view layout using our new text layout.
5213            requestLayout();
5214            invalidate();
5215        } else {
5216            // Dynamic width, so we have no choice but to request a new
5217            // view layout with a new text layout.
5218
5219            nullLayouts();
5220            requestLayout();
5221            invalidate();
5222        }
5223    }
5224
5225    /**
5226     * Returns true if anything changed.
5227     */
5228    private boolean bringTextIntoView() {
5229        int line = 0;
5230        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5231            line = mLayout.getLineCount() - 1;
5232        }
5233
5234        Layout.Alignment a = mLayout.getParagraphAlignment(line);
5235        int dir = mLayout.getParagraphDirection(line);
5236        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5237        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5238        int ht = mLayout.getHeight();
5239
5240        int scrollx, scrolly;
5241
5242        if (a == Layout.Alignment.ALIGN_CENTER) {
5243            /*
5244             * Keep centered if possible, or, if it is too wide to fit,
5245             * keep leading edge in view.
5246             */
5247
5248            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5249            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5250
5251            if (right - left < hspace) {
5252                scrollx = (right + left) / 2 - hspace / 2;
5253            } else {
5254                if (dir < 0) {
5255                    scrollx = right - hspace;
5256                } else {
5257                    scrollx = left;
5258                }
5259            }
5260        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
5261            /*
5262             * Keep leading edge in view.
5263             */
5264
5265            if (dir < 0) {
5266                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5267                scrollx = right - hspace;
5268            } else {
5269                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5270            }
5271        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
5272            /*
5273             * Keep trailing edge in view.
5274             */
5275
5276            if (dir < 0) {
5277                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5278            } else {
5279                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5280                scrollx = right - hspace;
5281            }
5282        }
5283
5284        if (ht < vspace) {
5285            scrolly = 0;
5286        } else {
5287            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5288                scrolly = ht - vspace;
5289            } else {
5290                scrolly = 0;
5291            }
5292        }
5293
5294        if (scrollx != mScrollX || scrolly != mScrollY) {
5295            scrollTo(scrollx, scrolly);
5296            return true;
5297        } else {
5298            return false;
5299        }
5300    }
5301
5302    /**
5303     * Move the point, specified by the offset, into the view if it is needed.
5304     * This has to be called after layout. Returns true if anything changed.
5305     */
5306    public boolean bringPointIntoView(int offset) {
5307        boolean changed = false;
5308
5309        int line = mLayout.getLineForOffset(offset);
5310
5311        // FIXME: Is it okay to truncate this, or should we round?
5312        final int x = (int)mLayout.getPrimaryHorizontal(offset);
5313        final int top = mLayout.getLineTop(line);
5314        final int bottom = mLayout.getLineTop(line+1);
5315
5316        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5317        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5318        int ht = mLayout.getHeight();
5319
5320        int grav;
5321
5322        switch (mLayout.getParagraphAlignment(line)) {
5323            case ALIGN_NORMAL:
5324                grav = 1;
5325                break;
5326
5327            case ALIGN_OPPOSITE:
5328                grav = -1;
5329                break;
5330
5331            default:
5332                grav = 0;
5333        }
5334
5335        grav *= mLayout.getParagraphDirection(line);
5336
5337        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5338        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5339
5340        int hslack = (bottom - top) / 2;
5341        int vslack = hslack;
5342
5343        if (vslack > vspace / 4)
5344            vslack = vspace / 4;
5345        if (hslack > hspace / 4)
5346            hslack = hspace / 4;
5347
5348        int hs = mScrollX;
5349        int vs = mScrollY;
5350
5351        if (top - vs < vslack)
5352            vs = top - vslack;
5353        if (bottom - vs > vspace - vslack)
5354            vs = bottom - (vspace - vslack);
5355        if (ht - vs < vspace)
5356            vs = ht - vspace;
5357        if (0 - vs > 0)
5358            vs = 0;
5359
5360        if (grav != 0) {
5361            if (x - hs < hslack) {
5362                hs = x - hslack;
5363            }
5364            if (x - hs > hspace - hslack) {
5365                hs = x - (hspace - hslack);
5366            }
5367        }
5368
5369        if (grav < 0) {
5370            if (left - hs > 0)
5371                hs = left;
5372            if (right - hs < hspace)
5373                hs = right - hspace;
5374        } else if (grav > 0) {
5375            if (right - hs < hspace)
5376                hs = right - hspace;
5377            if (left - hs > 0)
5378                hs = left;
5379        } else /* grav == 0 */ {
5380            if (right - left <= hspace) {
5381                /*
5382                 * If the entire text fits, center it exactly.
5383                 */
5384                hs = left - (hspace - (right - left)) / 2;
5385            } else if (x > right - hslack) {
5386                /*
5387                 * If we are near the right edge, keep the right edge
5388                 * at the edge of the view.
5389                 */
5390                hs = right - hspace;
5391            } else if (x < left + hslack) {
5392                /*
5393                 * If we are near the left edge, keep the left edge
5394                 * at the edge of the view.
5395                 */
5396                hs = left;
5397            } else if (left > hs) {
5398                /*
5399                 * Is there whitespace visible at the left?  Fix it if so.
5400                 */
5401                hs = left;
5402            } else if (right < hs + hspace) {
5403                /*
5404                 * Is there whitespace visible at the right?  Fix it if so.
5405                 */
5406                hs = right - hspace;
5407            } else {
5408                /*
5409                 * Otherwise, float as needed.
5410                 */
5411                if (x - hs < hslack) {
5412                    hs = x - hslack;
5413                }
5414                if (x - hs > hspace - hslack) {
5415                    hs = x - (hspace - hslack);
5416                }
5417            }
5418        }
5419
5420        if (hs != mScrollX || vs != mScrollY) {
5421            if (mScroller == null) {
5422                scrollTo(hs, vs);
5423            } else {
5424                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
5425                int dx = hs - mScrollX;
5426                int dy = vs - mScrollY;
5427
5428                if (duration > ANIMATED_SCROLL_GAP) {
5429                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
5430                    invalidate();
5431                } else {
5432                    if (!mScroller.isFinished()) {
5433                        mScroller.abortAnimation();
5434                    }
5435
5436                    scrollBy(dx, dy);
5437                }
5438
5439                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
5440            }
5441
5442            changed = true;
5443        }
5444
5445        if (isFocused()) {
5446            // This offsets because getInterestingRect() is in terms of
5447            // viewport coordinates, but requestRectangleOnScreen()
5448            // is in terms of content coordinates.
5449
5450            Rect r = new Rect();
5451            getInterestingRect(r, x, top, bottom, line);
5452            r.offset(mScrollX, mScrollY);
5453
5454            if (requestRectangleOnScreen(r)) {
5455                changed = true;
5456            }
5457        }
5458
5459        return changed;
5460    }
5461
5462    /**
5463     * Move the cursor, if needed, so that it is at an offset that is visible
5464     * to the user.  This will not move the cursor if it represents more than
5465     * one character (a selection range).  This will only work if the
5466     * TextView contains spannable text; otherwise it will do nothing.
5467     */
5468    public boolean moveCursorToVisibleOffset() {
5469        if (!(mText instanceof Spannable)) {
5470            return false;
5471        }
5472        int start = Selection.getSelectionStart(mText);
5473        int end = Selection.getSelectionEnd(mText);
5474        if (start != end) {
5475            return false;
5476        }
5477
5478        // First: make sure the line is visible on screen:
5479
5480        int line = mLayout.getLineForOffset(start);
5481
5482        final int top = mLayout.getLineTop(line);
5483        final int bottom = mLayout.getLineTop(line+1);
5484        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5485        int vslack = (bottom - top) / 2;
5486        if (vslack > vspace / 4)
5487            vslack = vspace / 4;
5488        final int vs = mScrollY;
5489
5490        if (top < (vs+vslack)) {
5491            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
5492        } else if (bottom > (vspace+vs-vslack)) {
5493            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
5494        }
5495
5496        // Next: make sure the character is visible on screen:
5497
5498        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5499        final int hs = mScrollX;
5500        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
5501        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
5502
5503        int newStart = start;
5504        if (newStart < leftChar) {
5505            newStart = leftChar;
5506        } else if (newStart > rightChar) {
5507            newStart = rightChar;
5508        }
5509
5510        if (newStart != start) {
5511            Selection.setSelection((Spannable)mText, newStart);
5512            return true;
5513        }
5514
5515        return false;
5516    }
5517
5518    @Override
5519    public void computeScroll() {
5520        if (mScroller != null) {
5521            if (mScroller.computeScrollOffset()) {
5522                mScrollX = mScroller.getCurrX();
5523                mScrollY = mScroller.getCurrY();
5524                postInvalidate();  // So we draw again
5525            }
5526        }
5527    }
5528
5529    private void getInterestingRect(Rect r, int h, int top, int bottom,
5530                                    int line) {
5531        int paddingTop = getExtendedPaddingTop();
5532        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5533            paddingTop += getVerticalOffset(false);
5534        }
5535        top += paddingTop;
5536        bottom += paddingTop;
5537        h += getCompoundPaddingLeft();
5538
5539        if (line == 0)
5540            top -= getExtendedPaddingTop();
5541        if (line == mLayout.getLineCount() - 1)
5542            bottom += getExtendedPaddingBottom();
5543
5544        r.set(h, top, h+1, bottom);
5545        r.offset(-mScrollX, -mScrollY);
5546    }
5547
5548    @Override
5549    public void debug(int depth) {
5550        super.debug(depth);
5551
5552        String output = debugIndent(depth);
5553        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
5554                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
5555                + "} ";
5556
5557        if (mText != null) {
5558
5559            output += "mText=\"" + mText + "\" ";
5560            if (mLayout != null) {
5561                output += "mLayout width=" + mLayout.getWidth()
5562                        + " height=" + mLayout.getHeight();
5563            }
5564        } else {
5565            output += "mText=NULL";
5566        }
5567        Log.d(VIEW_LOG_TAG, output);
5568    }
5569
5570    /**
5571     * Convenience for {@link Selection#getSelectionStart}.
5572     */
5573    public int getSelectionStart() {
5574        return Selection.getSelectionStart(getText());
5575    }
5576
5577    /**
5578     * Convenience for {@link Selection#getSelectionEnd}.
5579     */
5580    public int getSelectionEnd() {
5581        return Selection.getSelectionEnd(getText());
5582    }
5583
5584    /**
5585     * Return true iff there is a selection inside this text view.
5586     */
5587    public boolean hasSelection() {
5588        return getSelectionStart() != getSelectionEnd();
5589    }
5590
5591    /**
5592     * Sets the properties of this field (lines, horizontally scrolling,
5593     * transformation method) to be for a single-line input.
5594     *
5595     * @attr ref android.R.styleable#TextView_singleLine
5596     */
5597    public void setSingleLine() {
5598        setSingleLine(true);
5599    }
5600
5601    /**
5602     * If true, sets the properties of this field (lines, horizontally
5603     * scrolling, transformation method) to be for a single-line input;
5604     * if false, restores these to the default conditions.
5605     * Note that calling this with false restores default conditions,
5606     * not necessarily those that were in effect prior to calling
5607     * it with true.
5608     *
5609     * @attr ref android.R.styleable#TextView_singleLine
5610     */
5611    @android.view.RemotableViewMethod
5612    public void setSingleLine(boolean singleLine) {
5613        if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
5614                == EditorInfo.TYPE_CLASS_TEXT) {
5615            if (singleLine) {
5616                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5617            } else {
5618                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5619            }
5620        }
5621        applySingleLine(singleLine, true);
5622    }
5623
5624    private void applySingleLine(boolean singleLine, boolean applyTransformation) {
5625        mSingleLine = singleLine;
5626        if (singleLine) {
5627            setLines(1);
5628            setHorizontallyScrolling(true);
5629            if (applyTransformation) {
5630                setTransformationMethod(SingleLineTransformationMethod.
5631                                        getInstance());
5632            }
5633        } else {
5634            setMaxLines(Integer.MAX_VALUE);
5635            setHorizontallyScrolling(false);
5636            if (applyTransformation) {
5637                setTransformationMethod(null);
5638            }
5639        }
5640    }
5641
5642    /**
5643     * Causes words in the text that are longer than the view is wide
5644     * to be ellipsized instead of broken in the middle.  You may also
5645     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
5646     * to constrain the text toa single line.  Use <code>null</code>
5647     * to turn off ellipsizing.
5648     *
5649     * @attr ref android.R.styleable#TextView_ellipsize
5650     */
5651    public void setEllipsize(TextUtils.TruncateAt where) {
5652        mEllipsize = where;
5653
5654        if (mLayout != null) {
5655            nullLayouts();
5656            requestLayout();
5657            invalidate();
5658        }
5659    }
5660
5661    /**
5662     * Sets how many times to repeat the marquee animation. Only applied if the
5663     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
5664     *
5665     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
5666     */
5667    public void setMarqueeRepeatLimit(int marqueeLimit) {
5668        mMarqueeRepeatLimit = marqueeLimit;
5669    }
5670
5671    /**
5672     * Returns where, if anywhere, words that are longer than the view
5673     * is wide should be ellipsized.
5674     */
5675    @ViewDebug.ExportedProperty
5676    public TextUtils.TruncateAt getEllipsize() {
5677        return mEllipsize;
5678    }
5679
5680    /**
5681     * Set the TextView so that when it takes focus, all the text is
5682     * selected.
5683     *
5684     * @attr ref android.R.styleable#TextView_selectAllOnFocus
5685     */
5686    @android.view.RemotableViewMethod
5687    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
5688        mSelectAllOnFocus = selectAllOnFocus;
5689
5690        if (selectAllOnFocus && !(mText instanceof Spannable)) {
5691            setText(mText, BufferType.SPANNABLE);
5692        }
5693    }
5694
5695    /**
5696     * Set whether the cursor is visible.  The default is true.
5697     *
5698     * @attr ref android.R.styleable#TextView_cursorVisible
5699     */
5700    @android.view.RemotableViewMethod
5701    public void setCursorVisible(boolean visible) {
5702        mCursorVisible = visible;
5703        invalidate();
5704
5705        if (visible) {
5706            makeBlink();
5707        } else if (mBlink != null) {
5708            mBlink.removeCallbacks(mBlink);
5709        }
5710    }
5711
5712    private boolean canMarquee() {
5713        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
5714        return width > 0 && mLayout.getLineWidth(0) > width;
5715    }
5716
5717    private void startMarquee() {
5718        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5719            return;
5720        }
5721
5722        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
5723                getLineCount() == 1 && canMarquee()) {
5724
5725            if (mMarquee == null) mMarquee = new Marquee(this);
5726            mMarquee.start(mMarqueeRepeatLimit);
5727        }
5728    }
5729
5730    private void stopMarquee() {
5731        if (mMarquee != null && !mMarquee.isStopped()) {
5732            mMarquee.stop();
5733        }
5734    }
5735
5736    private void startStopMarquee(boolean start) {
5737        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5738            if (start) {
5739                startMarquee();
5740            } else {
5741                stopMarquee();
5742            }
5743        }
5744    }
5745
5746    private static final class Marquee extends Handler {
5747        // TODO: Add an option to configure this
5748        private static final float MARQUEE_DELTA_MAX = 0.07f;
5749        private static final int MARQUEE_DELAY = 1200;
5750        private static final int MARQUEE_RESTART_DELAY = 1200;
5751        private static final int MARQUEE_RESOLUTION = 1000 / 30;
5752        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
5753
5754        private static final byte MARQUEE_STOPPED = 0x0;
5755        private static final byte MARQUEE_STARTING = 0x1;
5756        private static final byte MARQUEE_RUNNING = 0x2;
5757
5758        private static final int MESSAGE_START = 0x1;
5759        private static final int MESSAGE_TICK = 0x2;
5760        private static final int MESSAGE_RESTART = 0x3;
5761
5762        private final WeakReference<TextView> mView;
5763
5764        private byte mStatus = MARQUEE_STOPPED;
5765        private float mScrollUnit;
5766        private float mMaxScroll;
5767        float mMaxFadeScroll;
5768        private float mGhostStart;
5769        private float mGhostOffset;
5770        private float mFadeStop;
5771        private int mRepeatLimit;
5772
5773        float mScroll;
5774
5775        Marquee(TextView v) {
5776            final float density = v.getContext().getResources().getDisplayMetrics().density;
5777            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
5778            mView = new WeakReference<TextView>(v);
5779        }
5780
5781        @Override
5782        public void handleMessage(Message msg) {
5783            switch (msg.what) {
5784                case MESSAGE_START:
5785                    mStatus = MARQUEE_RUNNING;
5786                    tick();
5787                    break;
5788                case MESSAGE_TICK:
5789                    tick();
5790                    break;
5791                case MESSAGE_RESTART:
5792                    if (mStatus == MARQUEE_RUNNING) {
5793                        if (mRepeatLimit >= 0) {
5794                            mRepeatLimit--;
5795                        }
5796                        start(mRepeatLimit);
5797                    }
5798                    break;
5799            }
5800        }
5801
5802        void tick() {
5803            if (mStatus != MARQUEE_RUNNING) {
5804                return;
5805            }
5806
5807            removeMessages(MESSAGE_TICK);
5808
5809            final TextView textView = mView.get();
5810            if (textView != null && (textView.isFocused() || textView.isSelected())) {
5811                mScroll += mScrollUnit;
5812                if (mScroll > mMaxScroll) {
5813                    mScroll = mMaxScroll;
5814                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
5815                } else {
5816                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
5817                }
5818                textView.invalidate();
5819            }
5820        }
5821
5822        void stop() {
5823            mStatus = MARQUEE_STOPPED;
5824            removeMessages(MESSAGE_START);
5825            removeMessages(MESSAGE_RESTART);
5826            removeMessages(MESSAGE_TICK);
5827            resetScroll();
5828        }
5829
5830        private void resetScroll() {
5831            mScroll = 0.0f;
5832            final TextView textView = mView.get();
5833            if (textView != null) textView.invalidate();
5834        }
5835
5836        void start(int repeatLimit) {
5837            if (repeatLimit == 0) {
5838                stop();
5839                return;
5840            }
5841            mRepeatLimit = repeatLimit;
5842            final TextView textView = mView.get();
5843            if (textView != null && textView.mLayout != null) {
5844                mStatus = MARQUEE_STARTING;
5845                mScroll = 0.0f;
5846                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
5847                        textView.getCompoundPaddingRight();
5848                final float lineWidth = textView.mLayout.getLineWidth(0);
5849                final float gap = textWidth / 3.0f;
5850                mGhostStart = lineWidth - textWidth + gap;
5851                mMaxScroll = mGhostStart + textWidth;
5852                mGhostOffset = lineWidth + gap;
5853                mFadeStop = lineWidth + textWidth / 6.0f;
5854                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
5855
5856                textView.invalidate();
5857                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
5858            }
5859        }
5860
5861        float getGhostOffset() {
5862            return mGhostOffset;
5863        }
5864
5865        boolean shouldDrawLeftFade() {
5866            return mScroll <= mFadeStop;
5867        }
5868
5869        boolean shouldDrawGhost() {
5870            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
5871        }
5872
5873        boolean isRunning() {
5874            return mStatus == MARQUEE_RUNNING;
5875        }
5876
5877        boolean isStopped() {
5878            return mStatus == MARQUEE_STOPPED;
5879        }
5880    }
5881
5882    /**
5883     * This method is called when the text is changed, in case any
5884     * subclasses would like to know.
5885     *
5886     * @param text The text the TextView is displaying.
5887     * @param start The offset of the start of the range of the text
5888     *              that was modified.
5889     * @param before The offset of the former end of the range of the
5890     *               text that was modified.  If text was simply inserted,
5891     *               this will be the same as <code>start</code>.
5892     *               If text was replaced with new text or deleted, the
5893     *               length of the old text was <code>before-start</code>.
5894     * @param after The offset of the end of the range of the text
5895     *              that was modified.  If text was simply deleted,
5896     *              this will be the same as <code>start</code>.
5897     *              If text was replaced with new text or inserted,
5898     *              the length of the new text is <code>after-start</code>.
5899     */
5900    protected void onTextChanged(CharSequence text,
5901                                 int start, int before, int after) {
5902    }
5903
5904    /**
5905     * This method is called when the selection has changed, in case any
5906     * subclasses would like to know.
5907     *
5908     * @param selStart The new selection start location.
5909     * @param selEnd The new selection end location.
5910     */
5911    protected void onSelectionChanged(int selStart, int selEnd) {
5912    }
5913
5914    /**
5915     * Adds a TextWatcher to the list of those whose methods are called
5916     * whenever this TextView's text changes.
5917     * <p>
5918     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
5919     * not called after {@link #setText} calls.  Now, doing {@link #setText}
5920     * if there are any text changed listeners forces the buffer type to
5921     * Editable if it would not otherwise be and does call this method.
5922     */
5923    public void addTextChangedListener(TextWatcher watcher) {
5924        if (mListeners == null) {
5925            mListeners = new ArrayList<TextWatcher>();
5926        }
5927
5928        mListeners.add(watcher);
5929    }
5930
5931    /**
5932     * Removes the specified TextWatcher from the list of those whose
5933     * methods are called
5934     * whenever this TextView's text changes.
5935     */
5936    public void removeTextChangedListener(TextWatcher watcher) {
5937        if (mListeners != null) {
5938            int i = mListeners.indexOf(watcher);
5939
5940            if (i >= 0) {
5941                mListeners.remove(i);
5942            }
5943        }
5944    }
5945
5946    private void sendBeforeTextChanged(CharSequence text, int start, int before,
5947                                   int after) {
5948        if (mListeners != null) {
5949            final ArrayList<TextWatcher> list = mListeners;
5950            final int count = list.size();
5951            for (int i = 0; i < count; i++) {
5952                list.get(i).beforeTextChanged(text, start, before, after);
5953            }
5954        }
5955    }
5956
5957    /**
5958     * Not private so it can be called from an inner class without going
5959     * through a thunk.
5960     */
5961    void sendOnTextChanged(CharSequence text, int start, int before,
5962                                   int after) {
5963        if (mListeners != null) {
5964            final ArrayList<TextWatcher> list = mListeners;
5965            final int count = list.size();
5966            for (int i = 0; i < count; i++) {
5967                list.get(i).onTextChanged(text, start, before, after);
5968            }
5969        }
5970    }
5971
5972    /**
5973     * Not private so it can be called from an inner class without going
5974     * through a thunk.
5975     */
5976    void sendAfterTextChanged(Editable text) {
5977        if (mListeners != null) {
5978            final ArrayList<TextWatcher> list = mListeners;
5979            final int count = list.size();
5980            for (int i = 0; i < count; i++) {
5981                list.get(i).afterTextChanged(text);
5982            }
5983        }
5984    }
5985
5986    /**
5987     * Not private so it can be called from an inner class without going
5988     * through a thunk.
5989     */
5990    void handleTextChanged(CharSequence buffer, int start,
5991            int before, int after) {
5992        final InputMethodState ims = mInputMethodState;
5993        if (ims == null || ims.mBatchEditNesting == 0) {
5994            updateAfterEdit();
5995        }
5996        if (ims != null) {
5997            ims.mContentChanged = true;
5998            if (ims.mChangedStart < 0) {
5999                ims.mChangedStart = start;
6000                ims.mChangedEnd = start+before;
6001            } else {
6002                if (ims.mChangedStart > start) ims.mChangedStart = start;
6003                if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
6004            }
6005            ims.mChangedDelta += after-before;
6006        }
6007
6008        sendOnTextChanged(buffer, start, before, after);
6009        onTextChanged(buffer, start, before, after);
6010    }
6011
6012    /**
6013     * Not private so it can be called from an inner class without going
6014     * through a thunk.
6015     */
6016    void spanChange(Spanned buf, Object what, int oldStart, int newStart,
6017            int oldEnd, int newEnd) {
6018        // XXX Make the start and end move together if this ends up
6019        // spending too much time invalidating.
6020
6021        boolean selChanged = false;
6022        int newSelStart=-1, newSelEnd=-1;
6023
6024        final InputMethodState ims = mInputMethodState;
6025
6026        if (what == Selection.SELECTION_END) {
6027            mHighlightPathBogus = true;
6028            selChanged = true;
6029            newSelEnd = newStart;
6030
6031            if (!isFocused()) {
6032                mSelectionMoved = true;
6033            }
6034
6035            if (oldStart >= 0 || newStart >= 0) {
6036                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6037                registerForPreDraw();
6038
6039                if (isFocused()) {
6040                    mShowCursor = SystemClock.uptimeMillis();
6041                    makeBlink();
6042                }
6043            }
6044        }
6045
6046        if (what == Selection.SELECTION_START) {
6047            mHighlightPathBogus = true;
6048            selChanged = true;
6049            newSelStart = newStart;
6050
6051            if (!isFocused()) {
6052                mSelectionMoved = true;
6053            }
6054
6055            if (oldStart >= 0 || newStart >= 0) {
6056                int end = Selection.getSelectionEnd(buf);
6057                invalidateCursor(end, oldStart, newStart);
6058            }
6059        }
6060
6061        if (selChanged) {
6062            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6063                if (newSelStart < 0) {
6064                    newSelStart = Selection.getSelectionStart(buf);
6065                }
6066                if (newSelEnd < 0) {
6067                    newSelEnd = Selection.getSelectionEnd(buf);
6068                }
6069                onSelectionChanged(newSelStart, newSelEnd);
6070            }
6071        }
6072
6073        if (what instanceof UpdateAppearance ||
6074            what instanceof ParagraphStyle) {
6075            if (ims == null || ims.mBatchEditNesting == 0) {
6076                invalidate();
6077                mHighlightPathBogus = true;
6078                checkForResize();
6079            } else {
6080                ims.mContentChanged = true;
6081            }
6082        }
6083
6084        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6085            mHighlightPathBogus = true;
6086            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6087                ims.mSelectionModeChanged = true;
6088            }
6089
6090            if (Selection.getSelectionStart(buf) >= 0) {
6091                if (ims == null || ims.mBatchEditNesting == 0) {
6092                    invalidateCursor();
6093                } else {
6094                    ims.mCursorChanged = true;
6095                }
6096            }
6097        }
6098
6099        if (what instanceof ParcelableSpan) {
6100            // If this is a span that can be sent to a remote process,
6101            // the current extract editor would be interested in it.
6102            if (ims != null && ims.mExtracting != null) {
6103                if (ims.mBatchEditNesting != 0) {
6104                    if (oldStart >= 0) {
6105                        if (ims.mChangedStart > oldStart) {
6106                            ims.mChangedStart = oldStart;
6107                        }
6108                        if (ims.mChangedStart > oldEnd) {
6109                            ims.mChangedStart = oldEnd;
6110                        }
6111                    }
6112                    if (newStart >= 0) {
6113                        if (ims.mChangedStart > newStart) {
6114                            ims.mChangedStart = newStart;
6115                        }
6116                        if (ims.mChangedStart > newEnd) {
6117                            ims.mChangedStart = newEnd;
6118                        }
6119                    }
6120                } else {
6121                    if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
6122                            + oldStart + "-" + oldEnd + ","
6123                            + newStart + "-" + newEnd + what);
6124                    ims.mContentChanged = true;
6125                }
6126            }
6127        }
6128    }
6129
6130    private class ChangeWatcher
6131    implements TextWatcher, SpanWatcher {
6132        public void beforeTextChanged(CharSequence buffer, int start,
6133                                      int before, int after) {
6134            if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
6135                    + " before=" + before + " after=" + after + ": " + buffer);
6136            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
6137        }
6138
6139        public void onTextChanged(CharSequence buffer, int start,
6140                                  int before, int after) {
6141            if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
6142                    + " before=" + before + " after=" + after + ": " + buffer);
6143            TextView.this.handleTextChanged(buffer, start, before, after);
6144        }
6145
6146        public void afterTextChanged(Editable buffer) {
6147            if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
6148            TextView.this.sendAfterTextChanged(buffer);
6149
6150            if (MetaKeyKeyListener.getMetaState(buffer,
6151                                 MetaKeyKeyListener.META_SELECTING) != 0) {
6152                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
6153            }
6154        }
6155
6156        public void onSpanChanged(Spannable buf,
6157                                  Object what, int s, int e, int st, int en) {
6158            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
6159                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
6160            TextView.this.spanChange(buf, what, s, st, e, en);
6161        }
6162
6163        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
6164            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
6165                    + " what=" + what + ": " + buf);
6166            TextView.this.spanChange(buf, what, -1, s, -1, e);
6167        }
6168
6169        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
6170            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
6171                    + " what=" + what + ": " + buf);
6172            TextView.this.spanChange(buf, what, s, -1, e, -1);
6173        }
6174    }
6175
6176    private void makeBlink() {
6177        if (!mCursorVisible) {
6178            if (mBlink != null) {
6179                mBlink.removeCallbacks(mBlink);
6180            }
6181
6182            return;
6183        }
6184
6185        if (mBlink == null)
6186            mBlink = new Blink(this);
6187
6188        mBlink.removeCallbacks(mBlink);
6189        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
6190    }
6191
6192    @Override
6193    public void onStartTemporaryDetach() {
6194        mTemporaryDetach = true;
6195    }
6196
6197    @Override
6198    public void onFinishTemporaryDetach() {
6199        mTemporaryDetach = false;
6200    }
6201
6202    @Override
6203    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
6204        if (mTemporaryDetach) {
6205            // If we are temporarily in the detach state, then do nothing.
6206            super.onFocusChanged(focused, direction, previouslyFocusedRect);
6207            return;
6208        }
6209
6210        mShowCursor = SystemClock.uptimeMillis();
6211
6212        ensureEndedBatchEdit();
6213
6214        if (focused) {
6215            int selStart = getSelectionStart();
6216            int selEnd = getSelectionEnd();
6217
6218            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
6219                boolean selMoved = mSelectionMoved;
6220
6221                if (mMovement != null) {
6222                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
6223                }
6224
6225                if (mSelectAllOnFocus) {
6226                    Selection.setSelection((Spannable) mText, 0, mText.length());
6227                }
6228
6229                if (selMoved && selStart >= 0 && selEnd >= 0) {
6230                    /*
6231                     * Someone intentionally set the selection, so let them
6232                     * do whatever it is that they wanted to do instead of
6233                     * the default on-focus behavior.  We reset the selection
6234                     * here instead of just skipping the onTakeFocus() call
6235                     * because some movement methods do something other than
6236                     * just setting the selection in theirs and we still
6237                     * need to go through that path.
6238                     */
6239
6240                    Selection.setSelection((Spannable) mText, selStart, selEnd);
6241                }
6242                mTouchFocusSelected = true;
6243            }
6244
6245            mFrozenWithFocus = false;
6246            mSelectionMoved = false;
6247
6248            if (mText instanceof Spannable) {
6249                Spannable sp = (Spannable) mText;
6250                MetaKeyKeyListener.resetMetaState(sp);
6251            }
6252
6253            makeBlink();
6254
6255            if (mError != null) {
6256                showError();
6257            }
6258        } else {
6259            if (mError != null) {
6260                hideError();
6261            }
6262            // Don't leave us in the middle of a batch edit.
6263            onEndBatchEdit();
6264        }
6265
6266        startStopMarquee(focused);
6267
6268        if (mTransformation != null) {
6269            mTransformation.onFocusChanged(this, mText, focused, direction,
6270                                           previouslyFocusedRect);
6271        }
6272
6273        super.onFocusChanged(focused, direction, previouslyFocusedRect);
6274    }
6275
6276    @Override
6277    public void onWindowFocusChanged(boolean hasWindowFocus) {
6278        super.onWindowFocusChanged(hasWindowFocus);
6279
6280        if (hasWindowFocus) {
6281            if (mBlink != null) {
6282                mBlink.uncancel();
6283
6284                if (isFocused()) {
6285                    mShowCursor = SystemClock.uptimeMillis();
6286                    makeBlink();
6287                }
6288            }
6289        } else {
6290            if (mBlink != null) {
6291                mBlink.cancel();
6292            }
6293            // Don't leave us in the middle of a batch edit.
6294            onEndBatchEdit();
6295            if (mInputContentType != null) {
6296                mInputContentType.enterDown = false;
6297            }
6298        }
6299
6300        startStopMarquee(hasWindowFocus);
6301    }
6302
6303    /**
6304     * Use {@link BaseInputConnection#removeComposingSpans
6305     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
6306     * state from this text view.
6307     */
6308    public void clearComposingText() {
6309        if (mText instanceof Spannable) {
6310            BaseInputConnection.removeComposingSpans((Spannable)mText);
6311        }
6312    }
6313
6314    @Override
6315    public void setSelected(boolean selected) {
6316        boolean wasSelected = isSelected();
6317
6318        super.setSelected(selected);
6319
6320        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6321            if (selected) {
6322                startMarquee();
6323            } else {
6324                stopMarquee();
6325            }
6326        }
6327    }
6328
6329    class CommitSelectionReceiver extends ResultReceiver {
6330        int mNewStart;
6331        int mNewEnd;
6332
6333        CommitSelectionReceiver() {
6334            super(getHandler());
6335        }
6336
6337        protected void onReceiveResult(int resultCode, Bundle resultData) {
6338            if (resultCode != InputMethodManager.RESULT_SHOWN) {
6339                Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
6340            }
6341        }
6342    }
6343
6344    @Override
6345    public boolean onTouchEvent(MotionEvent event) {
6346        final int action = event.getAction();
6347        if (action == MotionEvent.ACTION_DOWN) {
6348            // Reset this state; it will be re-set if super.onTouchEvent
6349            // causes focus to move to the view.
6350            mTouchFocusSelected = false;
6351        }
6352
6353        final boolean superResult = super.onTouchEvent(event);
6354
6355        /*
6356         * Don't handle the release after a long press, because it will
6357         * move the selection away from whatever the menu action was
6358         * trying to affect.
6359         */
6360        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
6361            mEatTouchRelease = false;
6362            return superResult;
6363        }
6364
6365        if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
6366
6367            if (action == MotionEvent.ACTION_DOWN) {
6368                mScrolled = false;
6369            }
6370
6371            boolean handled = false;
6372
6373            int oldSelStart = Selection.getSelectionStart(mText);
6374            int oldSelEnd = Selection.getSelectionEnd(mText);
6375
6376            if (mMovement != null) {
6377                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
6378            }
6379
6380            if (mText instanceof Editable && onCheckIsTextEditor()) {
6381                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
6382                    InputMethodManager imm = (InputMethodManager)
6383                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
6384
6385                    // This is going to be gross...  if tapping on the text view
6386                    // causes the IME to be displayed, we don't want the selection
6387                    // to change.  But the selection has already changed, and
6388                    // we won't know right away whether the IME is getting
6389                    // displayed, so...
6390
6391                    int newSelStart = Selection.getSelectionStart(mText);
6392                    int newSelEnd = Selection.getSelectionEnd(mText);
6393                    CommitSelectionReceiver csr = null;
6394                    if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
6395                        csr = new CommitSelectionReceiver();
6396                        csr.mNewStart = newSelStart;
6397                        csr.mNewEnd = newSelEnd;
6398                    }
6399
6400                    if (imm.showSoftInput(this, 0, csr) && csr != null) {
6401                        // The IME might get shown -- revert to the old
6402                        // selection, and change to the new when we finally
6403                        // find out of it is okay.
6404                        Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
6405                        handled = true;
6406                    }
6407                }
6408            }
6409
6410            if (handled) {
6411                return true;
6412            }
6413        }
6414
6415        return superResult;
6416    }
6417
6418    /**
6419     * Returns true, only while processing a touch gesture, if the initial
6420     * touch down event caused focus to move to the text view and as a result
6421     * its selection changed.  Only valid while processing the touch gesture
6422     * of interest.
6423     */
6424    public boolean didTouchFocusSelect() {
6425        return mTouchFocusSelected;
6426    }
6427
6428    @Override
6429    public void cancelLongPress() {
6430        super.cancelLongPress();
6431        mScrolled = true;
6432    }
6433
6434    @Override
6435    public boolean onTrackballEvent(MotionEvent event) {
6436        if (mMovement != null && mText instanceof Spannable &&
6437            mLayout != null) {
6438            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
6439                return true;
6440            }
6441        }
6442
6443        return super.onTrackballEvent(event);
6444    }
6445
6446    public void setScroller(Scroller s) {
6447        mScroller = s;
6448    }
6449
6450    private static class Blink extends Handler implements Runnable {
6451        private WeakReference<TextView> mView;
6452        private boolean mCancelled;
6453
6454        public Blink(TextView v) {
6455            mView = new WeakReference<TextView>(v);
6456        }
6457
6458        public void run() {
6459            if (mCancelled) {
6460                return;
6461            }
6462
6463            removeCallbacks(Blink.this);
6464
6465            TextView tv = mView.get();
6466
6467            if (tv != null && tv.isFocused()) {
6468                int st = Selection.getSelectionStart(tv.mText);
6469                int en = Selection.getSelectionEnd(tv.mText);
6470
6471                if (st == en && st >= 0 && en >= 0) {
6472                    if (tv.mLayout != null) {
6473                        tv.invalidateCursorPath();
6474                    }
6475
6476                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
6477                }
6478            }
6479        }
6480
6481        void cancel() {
6482            if (!mCancelled) {
6483                removeCallbacks(Blink.this);
6484                mCancelled = true;
6485            }
6486        }
6487
6488        void uncancel() {
6489            mCancelled = false;
6490        }
6491    }
6492
6493    @Override
6494    protected float getLeftFadingEdgeStrength() {
6495        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6496            if (mMarquee != null && !mMarquee.isStopped()) {
6497                final Marquee marquee = mMarquee;
6498                if (marquee.shouldDrawLeftFade()) {
6499                    return marquee.mScroll / getHorizontalFadingEdgeLength();
6500                } else {
6501                    return 0.0f;
6502                }
6503            } else if (getLineCount() == 1) {
6504                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6505                    case Gravity.LEFT:
6506                        return 0.0f;
6507                    case Gravity.RIGHT:
6508                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
6509                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
6510                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
6511                    case Gravity.CENTER_HORIZONTAL:
6512                        return 0.0f;
6513                }
6514            }
6515        }
6516        return super.getLeftFadingEdgeStrength();
6517    }
6518
6519    @Override
6520    protected float getRightFadingEdgeStrength() {
6521        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6522            if (mMarquee != null && !mMarquee.isStopped()) {
6523                final Marquee marquee = mMarquee;
6524                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
6525            } else if (getLineCount() == 1) {
6526                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6527                    case Gravity.LEFT:
6528                        return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) -
6529                                getCompoundPaddingLeft() - getCompoundPaddingRight()) /
6530                                getHorizontalFadingEdgeLength();
6531                    case Gravity.RIGHT:
6532                        return 0.0f;
6533                    case Gravity.CENTER_HORIZONTAL:
6534                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
6535                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
6536                                getHorizontalFadingEdgeLength();
6537                }
6538            }
6539        }
6540        return super.getRightFadingEdgeStrength();
6541    }
6542
6543    @Override
6544    protected int computeHorizontalScrollRange() {
6545        if (mLayout != null)
6546            return mLayout.getWidth();
6547
6548        return super.computeHorizontalScrollRange();
6549    }
6550
6551    @Override
6552    protected int computeVerticalScrollRange() {
6553        if (mLayout != null)
6554            return mLayout.getHeight();
6555
6556        return super.computeVerticalScrollRange();
6557    }
6558
6559    @Override
6560    protected int computeVerticalScrollExtent() {
6561        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
6562    }
6563
6564    public enum BufferType {
6565        NORMAL, SPANNABLE, EDITABLE,
6566    }
6567
6568    /**
6569     * Returns the TextView_textColor attribute from the
6570     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
6571     * from the TextView_textAppearance attribute, if TextView_textColor
6572     * was not set directly.
6573     */
6574    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
6575        ColorStateList colors;
6576        colors = attrs.getColorStateList(com.android.internal.R.styleable.
6577                                         TextView_textColor);
6578
6579        if (colors == null) {
6580            int ap = attrs.getResourceId(com.android.internal.R.styleable.
6581                                         TextView_textAppearance, -1);
6582            if (ap != -1) {
6583                TypedArray appearance;
6584                appearance = context.obtainStyledAttributes(ap,
6585                                            com.android.internal.R.styleable.TextAppearance);
6586                colors = appearance.getColorStateList(com.android.internal.R.styleable.
6587                                                  TextAppearance_textColor);
6588                appearance.recycle();
6589            }
6590        }
6591
6592        return colors;
6593    }
6594
6595    /**
6596     * Returns the default color from the TextView_textColor attribute
6597     * from the AttributeSet, if set, or the default color from the
6598     * TextAppearance_textColor from the TextView_textAppearance attribute,
6599     * if TextView_textColor was not set directly.
6600     */
6601    public static int getTextColor(Context context,
6602                                   TypedArray attrs,
6603                                   int def) {
6604        ColorStateList colors = getTextColors(context, attrs);
6605
6606        if (colors == null) {
6607            return def;
6608        } else {
6609            return colors.getDefaultColor();
6610        }
6611    }
6612
6613    @Override
6614    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
6615        switch (keyCode) {
6616        case KeyEvent.KEYCODE_A:
6617            if (canSelectAll()) {
6618                return onTextContextMenuItem(ID_SELECT_ALL);
6619            }
6620
6621            break;
6622
6623        case KeyEvent.KEYCODE_X:
6624            if (canCut()) {
6625                return onTextContextMenuItem(ID_CUT);
6626            }
6627
6628            break;
6629
6630        case KeyEvent.KEYCODE_C:
6631            if (canCopy()) {
6632                return onTextContextMenuItem(ID_COPY);
6633            }
6634
6635            break;
6636
6637        case KeyEvent.KEYCODE_V:
6638            if (canPaste()) {
6639                return onTextContextMenuItem(ID_PASTE);
6640            }
6641
6642            break;
6643        }
6644
6645        return super.onKeyShortcut(keyCode, event);
6646    }
6647
6648    private boolean canSelectAll() {
6649        if (mText instanceof Spannable && mText.length() != 0 &&
6650            mMovement != null && mMovement.canSelectArbitrarily()) {
6651            return true;
6652        }
6653
6654        return false;
6655    }
6656
6657    private boolean canSelectText() {
6658        if (mText instanceof Spannable && mText.length() != 0 &&
6659            mMovement != null && mMovement.canSelectArbitrarily()) {
6660            return true;
6661        }
6662
6663        return false;
6664    }
6665
6666    private boolean canCut() {
6667        if (mTransformation instanceof PasswordTransformationMethod) {
6668            return false;
6669        }
6670
6671        if (mText.length() > 0 && getSelectionStart() >= 0) {
6672            if (mText instanceof Editable && mInput != null) {
6673                return true;
6674            }
6675        }
6676
6677        return false;
6678    }
6679
6680    private boolean canCopy() {
6681        if (mTransformation instanceof PasswordTransformationMethod) {
6682            return false;
6683        }
6684
6685        if (mText.length() > 0 && getSelectionStart() >= 0) {
6686            return true;
6687        }
6688
6689        return false;
6690    }
6691
6692    private boolean canPaste() {
6693        if (mText instanceof Editable && mInput != null &&
6694            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
6695            ClipboardManager clip = (ClipboardManager)getContext()
6696                    .getSystemService(Context.CLIPBOARD_SERVICE);
6697            if (clip.hasText()) {
6698                return true;
6699            }
6700        }
6701
6702        return false;
6703    }
6704
6705    /**
6706     * Returns a word to add to the dictionary from the context menu,
6707     * or null if there is no cursor or no word at the cursor.
6708     */
6709    private String getWordForDictionary() {
6710        /*
6711         * Quick return if the input type is one where adding words
6712         * to the dictionary doesn't make any sense.
6713         */
6714        int klass = mInputType & InputType.TYPE_MASK_CLASS;
6715        if (klass == InputType.TYPE_CLASS_NUMBER ||
6716            klass == InputType.TYPE_CLASS_PHONE ||
6717            klass == InputType.TYPE_CLASS_DATETIME) {
6718            return null;
6719        }
6720
6721        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
6722        if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
6723            variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
6724            variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
6725            variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
6726            variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
6727            return null;
6728        }
6729
6730        int end = getSelectionEnd();
6731
6732        if (end < 0) {
6733            return null;
6734        }
6735
6736        int start = end;
6737        int len = mText.length();
6738
6739        for (; start > 0; start--) {
6740            char c = mTransformed.charAt(start - 1);
6741            int type = Character.getType(c);
6742
6743            if (c != '\'' &&
6744                type != Character.UPPERCASE_LETTER &&
6745                type != Character.LOWERCASE_LETTER &&
6746                type != Character.TITLECASE_LETTER &&
6747                type != Character.MODIFIER_LETTER &&
6748                type != Character.DECIMAL_DIGIT_NUMBER) {
6749                break;
6750            }
6751        }
6752
6753        for (; end < len; end++) {
6754            char c = mTransformed.charAt(end);
6755            int type = Character.getType(c);
6756
6757            if (c != '\'' &&
6758                type != Character.UPPERCASE_LETTER &&
6759                type != Character.LOWERCASE_LETTER &&
6760                type != Character.TITLECASE_LETTER &&
6761                type != Character.MODIFIER_LETTER &&
6762                type != Character.DECIMAL_DIGIT_NUMBER) {
6763                break;
6764            }
6765        }
6766
6767        if (start == end) {
6768            return null;
6769        }
6770
6771        if (end - start > 48) {
6772            return null;
6773        }
6774
6775        return TextUtils.substring(mTransformed, start, end);
6776    }
6777
6778    @Override
6779    protected void onCreateContextMenu(ContextMenu menu) {
6780        super.onCreateContextMenu(menu);
6781        boolean added = false;
6782
6783        if (!isFocused()) {
6784            if (isFocusable() && mInput != null) {
6785                if (canCopy()) {
6786                    MenuHandler handler = new MenuHandler();
6787                    int name = com.android.internal.R.string.copyAll;
6788
6789                    menu.add(0, ID_COPY, 0, name).
6790                        setOnMenuItemClickListener(handler).
6791                        setAlphabeticShortcut('c');
6792                    menu.setHeaderTitle(com.android.internal.R.string.
6793                        editTextMenuTitle);
6794                }
6795            }
6796
6797            return;
6798        }
6799
6800        MenuHandler handler = new MenuHandler();
6801
6802        if (canSelectAll()) {
6803            menu.add(0, ID_SELECT_ALL, 0,
6804                    com.android.internal.R.string.selectAll).
6805                setOnMenuItemClickListener(handler).
6806                setAlphabeticShortcut('a');
6807            added = true;
6808        }
6809
6810        boolean selection = getSelectionStart() != getSelectionEnd();
6811
6812        if (canSelectText()) {
6813            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
6814                menu.add(0, ID_STOP_SELECTING_TEXT, 0,
6815                        com.android.internal.R.string.stopSelectingText).
6816                    setOnMenuItemClickListener(handler);
6817                added = true;
6818            } else {
6819                menu.add(0, ID_START_SELECTING_TEXT, 0,
6820                        com.android.internal.R.string.selectText).
6821                    setOnMenuItemClickListener(handler);
6822                added = true;
6823            }
6824        }
6825
6826        if (canCut()) {
6827            int name;
6828            if (selection) {
6829                name = com.android.internal.R.string.cut;
6830            } else {
6831                name = com.android.internal.R.string.cutAll;
6832            }
6833
6834            menu.add(0, ID_CUT, 0, name).
6835                setOnMenuItemClickListener(handler).
6836                setAlphabeticShortcut('x');
6837            added = true;
6838        }
6839
6840        if (canCopy()) {
6841            int name;
6842            if (selection) {
6843                name = com.android.internal.R.string.copy;
6844            } else {
6845                name = com.android.internal.R.string.copyAll;
6846            }
6847
6848            menu.add(0, ID_COPY, 0, name).
6849                setOnMenuItemClickListener(handler).
6850                setAlphabeticShortcut('c');
6851            added = true;
6852        }
6853
6854        if (canPaste()) {
6855            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
6856                    setOnMenuItemClickListener(handler).
6857                    setAlphabeticShortcut('v');
6858            added = true;
6859        }
6860
6861        if (mText instanceof Spanned) {
6862            int selStart = getSelectionStart();
6863            int selEnd = getSelectionEnd();
6864
6865            int min = Math.min(selStart, selEnd);
6866            int max = Math.max(selStart, selEnd);
6867
6868            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
6869                                                        URLSpan.class);
6870            if (urls.length == 1) {
6871                menu.add(0, ID_COPY_URL, 0,
6872                         com.android.internal.R.string.copyUrl).
6873                            setOnMenuItemClickListener(handler);
6874                added = true;
6875            }
6876        }
6877
6878        if (isInputMethodTarget()) {
6879            menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
6880                    setOnMenuItemClickListener(handler);
6881            added = true;
6882        }
6883
6884        String word = getWordForDictionary();
6885        if (word != null) {
6886            menu.add(1, ID_ADD_TO_DICTIONARY, 0,
6887                     getContext().getString(com.android.internal.R.string.addToDictionary, word)).
6888                    setOnMenuItemClickListener(handler);
6889            added = true;
6890
6891        }
6892
6893        if (added) {
6894            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
6895        }
6896    }
6897
6898    /**
6899     * Returns whether this text view is a current input method target.  The
6900     * default implementation just checks with {@link InputMethodManager}.
6901     */
6902    public boolean isInputMethodTarget() {
6903        InputMethodManager imm = InputMethodManager.peekInstance();
6904        return imm != null && imm.isActive(this);
6905    }
6906
6907    private static final int ID_SELECT_ALL = android.R.id.selectAll;
6908    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
6909    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
6910    private static final int ID_CUT = android.R.id.cut;
6911    private static final int ID_COPY = android.R.id.copy;
6912    private static final int ID_PASTE = android.R.id.paste;
6913    private static final int ID_COPY_URL = android.R.id.copyUrl;
6914    private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
6915    private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
6916
6917    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
6918        public boolean onMenuItemClick(MenuItem item) {
6919            return onTextContextMenuItem(item.getItemId());
6920        }
6921    }
6922
6923    /**
6924     * Called when a context menu option for the text view is selected.  Currently
6925     * this will be one of: {@link android.R.id#selectAll},
6926     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
6927     * {@link android.R.id#cut}, {@link android.R.id#copy},
6928     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
6929     * or {@link android.R.id#switchInputMethod}.
6930     */
6931    public boolean onTextContextMenuItem(int id) {
6932        int selStart = getSelectionStart();
6933        int selEnd = getSelectionEnd();
6934
6935        if (!isFocused()) {
6936            selStart = 0;
6937            selEnd = mText.length();
6938        }
6939
6940        int min = Math.min(selStart, selEnd);
6941        int max = Math.max(selStart, selEnd);
6942
6943        if (min < 0) {
6944            min = 0;
6945        }
6946        if (max < 0) {
6947            max = 0;
6948        }
6949
6950        ClipboardManager clip = (ClipboardManager)getContext()
6951                .getSystemService(Context.CLIPBOARD_SERVICE);
6952
6953        switch (id) {
6954            case ID_SELECT_ALL:
6955                Selection.setSelection((Spannable) mText, 0,
6956                        mText.length());
6957                return true;
6958
6959            case ID_START_SELECTING_TEXT:
6960                MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
6961                return true;
6962
6963            case ID_STOP_SELECTING_TEXT:
6964                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6965                Selection.setSelection((Spannable) mText, getSelectionEnd());
6966                return true;
6967
6968            case ID_CUT:
6969                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6970
6971                if (min == max) {
6972                    min = 0;
6973                    max = mText.length();
6974                }
6975
6976                clip.setText(mTransformed.subSequence(min, max));
6977                ((Editable) mText).delete(min, max);
6978                return true;
6979
6980            case ID_COPY:
6981                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6982
6983                if (min == max) {
6984                    min = 0;
6985                    max = mText.length();
6986                }
6987
6988                clip.setText(mTransformed.subSequence(min, max));
6989                return true;
6990
6991            case ID_PASTE:
6992                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
6993
6994                CharSequence paste = clip.getText();
6995
6996                if (paste != null) {
6997                    Selection.setSelection((Spannable) mText, max);
6998                    ((Editable) mText).replace(min, max, paste);
6999                }
7000
7001                return true;
7002
7003            case ID_COPY_URL:
7004                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7005
7006                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7007                                                       URLSpan.class);
7008                if (urls.length == 1) {
7009                    clip.setText(urls[0].getURL());
7010                }
7011
7012                return true;
7013
7014            case ID_SWITCH_INPUT_METHOD:
7015                InputMethodManager imm = InputMethodManager.peekInstance();
7016                if (imm != null) {
7017                    imm.showInputMethodPicker();
7018                }
7019                return true;
7020
7021            case ID_ADD_TO_DICTIONARY:
7022                String word = getWordForDictionary();
7023
7024                if (word != null) {
7025                    Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
7026                    i.putExtra("word", word);
7027                    getContext().startActivity(i);
7028                }
7029
7030                return true;
7031            }
7032
7033        return false;
7034    }
7035
7036    public boolean performLongClick() {
7037        if (super.performLongClick()) {
7038            mEatTouchRelease = true;
7039            return true;
7040        }
7041
7042        return false;
7043    }
7044
7045    @ViewDebug.ExportedProperty
7046    private CharSequence            mText;
7047    private CharSequence            mTransformed;
7048    private BufferType              mBufferType = BufferType.NORMAL;
7049
7050    private int                     mInputType = EditorInfo.TYPE_NULL;
7051    private CharSequence            mHint;
7052    private Layout                  mHintLayout;
7053
7054    private KeyListener             mInput;
7055
7056    private MovementMethod          mMovement;
7057    private TransformationMethod    mTransformation;
7058    private ChangeWatcher           mChangeWatcher;
7059
7060    private ArrayList<TextWatcher>  mListeners = null;
7061
7062    // display attributes
7063    private TextPaint               mTextPaint;
7064    private boolean                 mUserSetTextScaleX;
7065    private Paint                   mHighlightPaint;
7066    private int                     mHighlightColor = 0xFFBBDDFF;
7067    private Layout                  mLayout;
7068
7069    private long                    mShowCursor;
7070    private Blink                   mBlink;
7071    private boolean                 mCursorVisible = true;
7072
7073    private boolean                 mSelectAllOnFocus = false;
7074
7075    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
7076    private boolean                 mHorizontallyScrolling;
7077
7078    private int                     mAutoLinkMask;
7079    private boolean                 mLinksClickable = true;
7080
7081    private float                   mSpacingMult = 1;
7082    private float                   mSpacingAdd = 0;
7083
7084    private static final int        LINES = 1;
7085    private static final int        EMS = LINES;
7086    private static final int        PIXELS = 2;
7087
7088    private int                     mMaximum = Integer.MAX_VALUE;
7089    private int                     mMaxMode = LINES;
7090    private int                     mMinimum = 0;
7091    private int                     mMinMode = LINES;
7092
7093    private int                     mMaxWidth = Integer.MAX_VALUE;
7094    private int                     mMaxWidthMode = PIXELS;
7095    private int                     mMinWidth = 0;
7096    private int                     mMinWidthMode = PIXELS;
7097
7098    private boolean                 mSingleLine;
7099    private int                     mDesiredHeightAtMeasure = -1;
7100    private boolean                 mIncludePad = true;
7101
7102    // tmp primitives, so we don't alloc them on each draw
7103    private Path                    mHighlightPath;
7104    private boolean                 mHighlightPathBogus = true;
7105    private static final RectF      sTempRect = new RectF();
7106
7107    // XXX should be much larger
7108    private static final int        VERY_WIDE = 16384;
7109
7110    private static final int        BLINK = 500;
7111
7112    private static final int ANIMATED_SCROLL_GAP = 250;
7113    private long mLastScroll;
7114    private Scroller mScroller = null;
7115
7116    private BoringLayout.Metrics mBoring;
7117    private BoringLayout.Metrics mHintBoring;
7118
7119    private BoringLayout mSavedLayout, mSavedHintLayout;
7120
7121    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
7122    private InputFilter[] mFilters = NO_FILTERS;
7123    private static final Spanned EMPTY_SPANNED = new SpannedString("");
7124}
7125