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