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