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