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