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