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