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