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