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