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