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