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