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