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