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