TextView.java revision 86b500289bffd8ce51e57e17d42741a202dd9ffd
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import com.android.internal.util.FastMath;
20import com.android.internal.widget.EditableInputConnection;
21
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.res.ColorStateList;
28import android.content.res.Resources;
29import android.content.res.TypedArray;
30import android.content.res.XmlResourceParser;
31import android.graphics.Canvas;
32import android.graphics.Paint;
33import android.graphics.Path;
34import android.graphics.Rect;
35import android.graphics.RectF;
36import android.graphics.Typeface;
37import android.graphics.drawable.Drawable;
38import android.inputmethodservice.ExtractEditText;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.Message;
42import android.os.Parcel;
43import android.os.Parcelable;
44import android.os.ResultReceiver;
45import android.os.SystemClock;
46import android.text.BoringLayout;
47import android.text.ClipboardManager;
48import android.text.DynamicLayout;
49import android.text.Editable;
50import android.text.GetChars;
51import android.text.GraphicsOperations;
52import android.text.InputFilter;
53import android.text.InputType;
54import android.text.Layout;
55import android.text.ParcelableSpan;
56import android.text.Selection;
57import android.text.SpanWatcher;
58import android.text.Spannable;
59import android.text.SpannableString;
60import android.text.Spanned;
61import android.text.SpannedString;
62import android.text.StaticLayout;
63import android.text.TextPaint;
64import android.text.TextUtils;
65import android.text.TextWatcher;
66import android.text.method.DateKeyListener;
67import android.text.method.DateTimeKeyListener;
68import android.text.method.DialerKeyListener;
69import android.text.method.DigitsKeyListener;
70import android.text.method.KeyListener;
71import android.text.method.LinkMovementMethod;
72import android.text.method.MetaKeyKeyListener;
73import android.text.method.MovementMethod;
74import android.text.method.PasswordTransformationMethod;
75import android.text.method.SingleLineTransformationMethod;
76import android.text.method.TextKeyListener;
77import android.text.method.TimeKeyListener;
78import android.text.method.TransformationMethod;
79import android.text.style.ParagraphStyle;
80import android.text.style.URLSpan;
81import android.text.style.UpdateAppearance;
82import android.text.util.Linkify;
83import android.util.AttributeSet;
84import android.util.FloatMath;
85import android.util.Log;
86import android.util.TypedValue;
87import android.view.ContextMenu;
88import android.view.Gravity;
89import android.view.KeyEvent;
90import android.view.LayoutInflater;
91import android.view.MenuItem;
92import android.view.MotionEvent;
93import android.view.View;
94import android.view.ViewDebug;
95import android.view.ViewGroup.LayoutParams;
96import android.view.ViewParent;
97import android.view.ViewRoot;
98import android.view.ViewTreeObserver;
99import android.view.accessibility.AccessibilityEvent;
100import android.view.accessibility.AccessibilityManager;
101import android.view.animation.AnimationUtils;
102import android.view.inputmethod.BaseInputConnection;
103import android.view.inputmethod.CompletionInfo;
104import android.view.inputmethod.EditorInfo;
105import android.view.inputmethod.ExtractedText;
106import android.view.inputmethod.ExtractedTextRequest;
107import android.view.inputmethod.InputConnection;
108import android.view.inputmethod.InputMethodManager;
109import android.widget.RemoteViews.RemoteView;
110
111import java.io.IOException;
112import java.lang.ref.WeakReference;
113import java.util.ArrayList;
114
115/**
116 * Displays text to the user and optionally allows them to edit it.  A TextView
117 * is a complete text editor, however the basic class is configured to not
118 * allow editing; see {@link EditText} for a subclass that configures the text
119 * view for editing.
120 *
121 * <p>
122 * <b>XML attributes</b>
123 * <p>
124 * See {@link android.R.styleable#TextView TextView Attributes},
125 * {@link android.R.styleable#View View Attributes}
126 *
127 * @attr ref android.R.styleable#TextView_text
128 * @attr ref android.R.styleable#TextView_bufferType
129 * @attr ref android.R.styleable#TextView_hint
130 * @attr ref android.R.styleable#TextView_textColor
131 * @attr ref android.R.styleable#TextView_textColorHighlight
132 * @attr ref android.R.styleable#TextView_textColorHint
133 * @attr ref android.R.styleable#TextView_textAppearance
134 * @attr ref android.R.styleable#TextView_textColorLink
135 * @attr ref android.R.styleable#TextView_textSize
136 * @attr ref android.R.styleable#TextView_textScaleX
137 * @attr ref android.R.styleable#TextView_typeface
138 * @attr ref android.R.styleable#TextView_textStyle
139 * @attr ref android.R.styleable#TextView_cursorVisible
140 * @attr ref android.R.styleable#TextView_maxLines
141 * @attr ref android.R.styleable#TextView_maxHeight
142 * @attr ref android.R.styleable#TextView_lines
143 * @attr ref android.R.styleable#TextView_height
144 * @attr ref android.R.styleable#TextView_minLines
145 * @attr ref android.R.styleable#TextView_minHeight
146 * @attr ref android.R.styleable#TextView_maxEms
147 * @attr ref android.R.styleable#TextView_maxWidth
148 * @attr ref android.R.styleable#TextView_ems
149 * @attr ref android.R.styleable#TextView_width
150 * @attr ref android.R.styleable#TextView_minEms
151 * @attr ref android.R.styleable#TextView_minWidth
152 * @attr ref android.R.styleable#TextView_gravity
153 * @attr ref android.R.styleable#TextView_scrollHorizontally
154 * @attr ref android.R.styleable#TextView_password
155 * @attr ref android.R.styleable#TextView_singleLine
156 * @attr ref android.R.styleable#TextView_selectAllOnFocus
157 * @attr ref android.R.styleable#TextView_includeFontPadding
158 * @attr ref android.R.styleable#TextView_maxLength
159 * @attr ref android.R.styleable#TextView_shadowColor
160 * @attr ref android.R.styleable#TextView_shadowDx
161 * @attr ref android.R.styleable#TextView_shadowDy
162 * @attr ref android.R.styleable#TextView_shadowRadius
163 * @attr ref android.R.styleable#TextView_autoLink
164 * @attr ref android.R.styleable#TextView_linksClickable
165 * @attr ref android.R.styleable#TextView_numeric
166 * @attr ref android.R.styleable#TextView_digits
167 * @attr ref android.R.styleable#TextView_phoneNumber
168 * @attr ref android.R.styleable#TextView_inputMethod
169 * @attr ref android.R.styleable#TextView_capitalize
170 * @attr ref android.R.styleable#TextView_autoText
171 * @attr ref android.R.styleable#TextView_editable
172 * @attr ref android.R.styleable#TextView_freezesText
173 * @attr ref android.R.styleable#TextView_ellipsize
174 * @attr ref android.R.styleable#TextView_drawableTop
175 * @attr ref android.R.styleable#TextView_drawableBottom
176 * @attr ref android.R.styleable#TextView_drawableRight
177 * @attr ref android.R.styleable#TextView_drawableLeft
178 * @attr ref android.R.styleable#TextView_drawablePadding
179 * @attr ref android.R.styleable#TextView_lineSpacingExtra
180 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
181 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
182 * @attr ref android.R.styleable#TextView_inputType
183 * @attr ref android.R.styleable#TextView_imeOptions
184 * @attr ref android.R.styleable#TextView_privateImeOptions
185 * @attr ref android.R.styleable#TextView_imeActionLabel
186 * @attr ref android.R.styleable#TextView_imeActionId
187 * @attr ref android.R.styleable#TextView_editorExtras
188 */
189@RemoteView
190public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
191    static final String LOG_TAG = "TextView";
192    static final boolean DEBUG_EXTRACT = false;
193
194    private static int PRIORITY = 100;
195
196    final int[] mTempCoords = new int[2];
197    Rect mTempRect;
198
199    private ColorStateList mTextColor;
200    private int mCurTextColor;
201    private ColorStateList mHintTextColor;
202    private ColorStateList mLinkTextColor;
203    private int mCurHintTextColor;
204    private boolean mFreezesText;
205    private boolean mFrozenWithFocus;
206    private boolean mTemporaryDetach;
207    private boolean mDispatchTemporaryDetach;
208
209    private boolean mEatTouchRelease = false;
210    private boolean mScrolled = false;
211
212    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
213    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
214
215    private float mShadowRadius, mShadowDx, mShadowDy;
216
217    private static final int PREDRAW_NOT_REGISTERED = 0;
218    private static final int PREDRAW_PENDING = 1;
219    private static final int PREDRAW_DONE = 2;
220    private int mPreDrawState = PREDRAW_NOT_REGISTERED;
221
222    private TextUtils.TruncateAt mEllipsize = null;
223
224    // Enum for the "typeface" XML parameter.
225    // TODO: How can we get this from the XML instead of hardcoding it here?
226    private static final int SANS = 1;
227    private static final int SERIF = 2;
228    private static final int MONOSPACE = 3;
229
230    // Bitfield for the "numeric" XML parameter.
231    // TODO: How can we get this from the XML instead of hardcoding it here?
232    private static final int SIGNED = 2;
233    private static final int DECIMAL = 4;
234
235    class Drawables {
236        final Rect mCompoundRect = new Rect();
237        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
238        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
239        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
240        int mDrawablePadding;
241    }
242    private Drawables mDrawables;
243
244    private CharSequence mError;
245    private boolean mErrorWasChanged;
246    private ErrorPopup mPopup;
247    /**
248     * This flag is set if the TextView tries to display an error before it
249     * is attached to the window (so its position is still unknown).
250     * It causes the error to be shown later, when onAttachedToWindow()
251     * is called.
252     */
253    private boolean mShowErrorAfterAttach;
254
255    private CharWrapper mCharWrapper = null;
256
257    private boolean mSelectionMoved = false;
258    private boolean mTouchFocusSelected = false;
259
260    private Marquee mMarquee;
261    private boolean mRestartMarquee;
262
263    private int mMarqueeRepeatLimit = 3;
264
265    class InputContentType {
266        int imeOptions = EditorInfo.IME_NULL;
267        String privateImeOptions;
268        CharSequence imeActionLabel;
269        int imeActionId;
270        Bundle extras;
271        OnEditorActionListener onEditorActionListener;
272        boolean enterDown;
273    }
274    InputContentType mInputContentType;
275
276    class InputMethodState {
277        Rect mCursorRectInWindow = new Rect();
278        RectF mTmpRectF = new RectF();
279        float[] mTmpOffset = new float[2];
280        ExtractedTextRequest mExtracting;
281        final ExtractedText mTmpExtracted = new ExtractedText();
282        int mBatchEditNesting;
283        boolean mCursorChanged;
284        boolean mSelectionModeChanged;
285        boolean mContentChanged;
286        int mChangedStart, mChangedEnd, mChangedDelta;
287    }
288    InputMethodState mInputMethodState;
289
290    int mTextSelectHandleLeftRes;
291    int mTextSelectHandleRightRes;
292    int mTextSelectHandleRes;
293
294    Drawable mSelectHandleLeft;
295    Drawable mSelectHandleRight;
296    Drawable mSelectHandleCenter;
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 float measureText(int start, int end, Paint p) {
2823            return p.measureText(mChars, start + mStart, end - start);
2824        }
2825
2826        public int getTextWidths(int start, int end, float[] widths, Paint p) {
2827            return p.getTextWidths(mChars, start + mStart, end - start, widths);
2828        }
2829    }
2830
2831    /**
2832     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2833     * except that the cursor position (if any) is retained in the new text.
2834     *
2835     * @see #setText(CharSequence, android.widget.TextView.BufferType)
2836     */
2837    public final void setTextKeepState(CharSequence text, BufferType type) {
2838        int start = getSelectionStart();
2839        int end = getSelectionEnd();
2840        int len = text.length();
2841
2842        setText(text, type);
2843
2844        if (start >= 0 || end >= 0) {
2845            if (mText instanceof Spannable) {
2846                Selection.setSelection((Spannable) mText,
2847                                       Math.max(0, Math.min(start, len)),
2848                                       Math.max(0, Math.min(end, len)));
2849            }
2850        }
2851    }
2852
2853    @android.view.RemotableViewMethod
2854    public final void setText(int resid) {
2855        setText(getContext().getResources().getText(resid));
2856    }
2857
2858    public final void setText(int resid, BufferType type) {
2859        setText(getContext().getResources().getText(resid), type);
2860    }
2861
2862    /**
2863     * Sets the text to be displayed when the text of the TextView is empty.
2864     * Null means to use the normal empty text. The hint does not currently
2865     * participate in determining the size of the view.
2866     *
2867     * @attr ref android.R.styleable#TextView_hint
2868     */
2869    @android.view.RemotableViewMethod
2870    public final void setHint(CharSequence hint) {
2871        mHint = TextUtils.stringOrSpannedString(hint);
2872
2873        if (mLayout != null) {
2874            checkForRelayout();
2875        }
2876
2877        if (mText.length() == 0) {
2878            invalidate();
2879        }
2880    }
2881
2882    /**
2883     * Sets the text to be displayed when the text of the TextView is empty,
2884     * from a resource.
2885     *
2886     * @attr ref android.R.styleable#TextView_hint
2887     */
2888    @android.view.RemotableViewMethod
2889    public final void setHint(int resid) {
2890        setHint(getContext().getResources().getText(resid));
2891    }
2892
2893    /**
2894     * Returns the hint that is displayed when the text of the TextView
2895     * is empty.
2896     *
2897     * @attr ref android.R.styleable#TextView_hint
2898     */
2899    @ViewDebug.CapturedViewProperty
2900    public CharSequence getHint() {
2901        return mHint;
2902    }
2903
2904    /**
2905     * Set the type of the content with a constant as defined for
2906     * {@link EditorInfo#inputType}.  This will take care of changing
2907     * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
2908     * match the given content type.  If the given content type is
2909     * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
2910     * not be displayed for this text view.
2911     *
2912     * @see #getInputType()
2913     * @see #setRawInputType(int)
2914     * @see android.text.InputType
2915     * @attr ref android.R.styleable#TextView_inputType
2916     */
2917    public void setInputType(int type) {
2918        final boolean wasPassword = isPasswordInputType(mInputType);
2919        final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
2920        setInputType(type, false);
2921        final boolean isPassword = isPasswordInputType(type);
2922        final boolean isVisiblePassword = isVisiblePasswordInputType(type);
2923        boolean forceUpdate = false;
2924        if (isPassword) {
2925            setTransformationMethod(PasswordTransformationMethod.getInstance());
2926            setTypefaceByIndex(MONOSPACE, 0);
2927        } else if (isVisiblePassword) {
2928            if (mTransformation == PasswordTransformationMethod.getInstance()) {
2929                forceUpdate = true;
2930            }
2931            setTypefaceByIndex(MONOSPACE, 0);
2932        } else if (wasPassword || wasVisiblePassword) {
2933            // not in password mode, clean up typeface and transformation
2934            setTypefaceByIndex(-1, -1);
2935            if (mTransformation == PasswordTransformationMethod.getInstance()) {
2936                forceUpdate = true;
2937            }
2938        }
2939
2940        boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
2941                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
2942                (EditorInfo.TYPE_CLASS_TEXT
2943                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
2944
2945        // We need to update the single line mode if it has changed or we
2946        // were previously in password mode.
2947        if (mSingleLine == multiLine || forceUpdate) {
2948            // Change single line mode, but only change the transformation if
2949            // we are not in password mode.
2950            applySingleLine(!multiLine, !isPassword);
2951        }
2952
2953        InputMethodManager imm = InputMethodManager.peekInstance();
2954        if (imm != null) imm.restartInput(this);
2955    }
2956
2957    private boolean isPasswordInputType(int inputType) {
2958        final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
2959                | EditorInfo.TYPE_MASK_VARIATION);
2960        return variation
2961                == (EditorInfo.TYPE_CLASS_TEXT
2962                        | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
2963    }
2964
2965    private boolean isVisiblePasswordInputType(int inputType) {
2966        final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
2967                | EditorInfo.TYPE_MASK_VARIATION);
2968        return variation
2969                == (EditorInfo.TYPE_CLASS_TEXT
2970                        | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
2971    }
2972
2973    /**
2974     * Directly change the content type integer of the text view, without
2975     * modifying any other state.
2976     * @see #setInputType(int)
2977     * @see android.text.InputType
2978     * @attr ref android.R.styleable#TextView_inputType
2979     */
2980    public void setRawInputType(int type) {
2981        mInputType = type;
2982    }
2983
2984    private void setInputType(int type, boolean direct) {
2985        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
2986        KeyListener input;
2987        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
2988            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
2989            TextKeyListener.Capitalize cap;
2990            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
2991                cap = TextKeyListener.Capitalize.CHARACTERS;
2992            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
2993                cap = TextKeyListener.Capitalize.WORDS;
2994            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
2995                cap = TextKeyListener.Capitalize.SENTENCES;
2996            } else {
2997                cap = TextKeyListener.Capitalize.NONE;
2998            }
2999            input = TextKeyListener.getInstance(autotext, cap);
3000        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
3001            input = DigitsKeyListener.getInstance(
3002                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
3003                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
3004        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
3005            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
3006                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
3007                    input = DateKeyListener.getInstance();
3008                    break;
3009                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
3010                    input = TimeKeyListener.getInstance();
3011                    break;
3012                default:
3013                    input = DateTimeKeyListener.getInstance();
3014                    break;
3015            }
3016        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
3017            input = DialerKeyListener.getInstance();
3018        } else {
3019            input = TextKeyListener.getInstance();
3020        }
3021        setRawInputType(type);
3022        if (direct) mInput = input;
3023        else {
3024            setKeyListenerOnly(input);
3025        }
3026    }
3027
3028    /**
3029     * Get the type of the content.
3030     *
3031     * @see #setInputType(int)
3032     * @see android.text.InputType
3033     */
3034    public int getInputType() {
3035        return mInputType;
3036    }
3037
3038    /**
3039     * Change the editor type integer associated with the text view, which
3040     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
3041     * has focus.
3042     * @see #getImeOptions
3043     * @see android.view.inputmethod.EditorInfo
3044     * @attr ref android.R.styleable#TextView_imeOptions
3045     */
3046    public void setImeOptions(int imeOptions) {
3047        if (mInputContentType == null) {
3048            mInputContentType = new InputContentType();
3049        }
3050        mInputContentType.imeOptions = imeOptions;
3051    }
3052
3053    /**
3054     * Get the type of the IME editor.
3055     *
3056     * @see #setImeOptions(int)
3057     * @see android.view.inputmethod.EditorInfo
3058     */
3059    public int getImeOptions() {
3060        return mInputContentType != null
3061                ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
3062    }
3063
3064    /**
3065     * Change the custom IME action associated with the text view, which
3066     * will be reported to an IME with {@link EditorInfo#actionLabel}
3067     * and {@link EditorInfo#actionId} when it has focus.
3068     * @see #getImeActionLabel
3069     * @see #getImeActionId
3070     * @see android.view.inputmethod.EditorInfo
3071     * @attr ref android.R.styleable#TextView_imeActionLabel
3072     * @attr ref android.R.styleable#TextView_imeActionId
3073     */
3074    public void setImeActionLabel(CharSequence label, int actionId) {
3075        if (mInputContentType == null) {
3076            mInputContentType = new InputContentType();
3077        }
3078        mInputContentType.imeActionLabel = label;
3079        mInputContentType.imeActionId = actionId;
3080    }
3081
3082    /**
3083     * Get the IME action label previous set with {@link #setImeActionLabel}.
3084     *
3085     * @see #setImeActionLabel
3086     * @see android.view.inputmethod.EditorInfo
3087     */
3088    public CharSequence getImeActionLabel() {
3089        return mInputContentType != null
3090                ? mInputContentType.imeActionLabel : null;
3091    }
3092
3093    /**
3094     * Get the IME action ID previous set with {@link #setImeActionLabel}.
3095     *
3096     * @see #setImeActionLabel
3097     * @see android.view.inputmethod.EditorInfo
3098     */
3099    public int getImeActionId() {
3100        return mInputContentType != null
3101                ? mInputContentType.imeActionId : 0;
3102    }
3103
3104    /**
3105     * Set a special listener to be called when an action is performed
3106     * on the text view.  This will be called when the enter key is pressed,
3107     * or when an action supplied to the IME is selected by the user.  Setting
3108     * this means that the normal hard key event will not insert a newline
3109     * into the text view, even if it is multi-line; holding down the ALT
3110     * modifier will, however, allow the user to insert a newline character.
3111     */
3112    public void setOnEditorActionListener(OnEditorActionListener l) {
3113        if (mInputContentType == null) {
3114            mInputContentType = new InputContentType();
3115        }
3116        mInputContentType.onEditorActionListener = l;
3117    }
3118
3119    /**
3120     * Called when an attached input method calls
3121     * {@link InputConnection#performEditorAction(int)
3122     * InputConnection.performEditorAction()}
3123     * for this text view.  The default implementation will call your action
3124     * listener supplied to {@link #setOnEditorActionListener}, or perform
3125     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
3126     * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
3127     * EditorInfo.IME_ACTION_DONE}.
3128     *
3129     * <p>For backwards compatibility, if no IME options have been set and the
3130     * text view would not normally advance focus on enter, then
3131     * the NEXT and DONE actions received here will be turned into an enter
3132     * key down/up pair to go through the normal key handling.
3133     *
3134     * @param actionCode The code of the action being performed.
3135     *
3136     * @see #setOnEditorActionListener
3137     */
3138    public void onEditorAction(int actionCode) {
3139        final InputContentType ict = mInputContentType;
3140        if (ict != null) {
3141            if (ict.onEditorActionListener != null) {
3142                if (ict.onEditorActionListener.onEditorAction(this,
3143                        actionCode, null)) {
3144                    return;
3145                }
3146            }
3147
3148            // This is the handling for some default action.
3149            // Note that for backwards compatibility we don't do this
3150            // default handling if explicit ime options have not been given,
3151            // instead turning this into the normal enter key codes that an
3152            // app may be expecting.
3153            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3154                View v = focusSearch(FOCUS_DOWN);
3155                if (v != null) {
3156                    if (!v.requestFocus(FOCUS_DOWN)) {
3157                        throw new IllegalStateException("focus search returned a view " +
3158                                "that wasn't able to take focus!");
3159                    }
3160                }
3161                return;
3162
3163            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3164                InputMethodManager imm = InputMethodManager.peekInstance();
3165                if (imm != null) {
3166                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
3167                }
3168                return;
3169            }
3170        }
3171
3172        Handler h = getHandler();
3173        if (h != null) {
3174            long eventTime = SystemClock.uptimeMillis();
3175            h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3176                    new KeyEvent(eventTime, eventTime,
3177                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3178                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3179                    | KeyEvent.FLAG_EDITOR_ACTION)));
3180            h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3181                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
3182                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3183                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3184                    | KeyEvent.FLAG_EDITOR_ACTION)));
3185        }
3186    }
3187
3188    /**
3189     * Set the private content type of the text, which is the
3190     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3191     * field that will be filled in when creating an input connection.
3192     *
3193     * @see #getPrivateImeOptions()
3194     * @see EditorInfo#privateImeOptions
3195     * @attr ref android.R.styleable#TextView_privateImeOptions
3196     */
3197    public void setPrivateImeOptions(String type) {
3198        if (mInputContentType == null) mInputContentType = new InputContentType();
3199        mInputContentType.privateImeOptions = type;
3200    }
3201
3202    /**
3203     * Get the private type of the content.
3204     *
3205     * @see #setPrivateImeOptions(String)
3206     * @see EditorInfo#privateImeOptions
3207     */
3208    public String getPrivateImeOptions() {
3209        return mInputContentType != null
3210                ? mInputContentType.privateImeOptions : null;
3211    }
3212
3213    /**
3214     * Set the extra input data of the text, which is the
3215     * {@link EditorInfo#extras TextBoxAttribute.extras}
3216     * Bundle that will be filled in when creating an input connection.  The
3217     * given integer is the resource ID of an XML resource holding an
3218     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3219     *
3220     * @see #getInputExtras(boolean)
3221     * @see EditorInfo#extras
3222     * @attr ref android.R.styleable#TextView_editorExtras
3223     */
3224    public void setInputExtras(int xmlResId)
3225            throws XmlPullParserException, IOException {
3226        XmlResourceParser parser = getResources().getXml(xmlResId);
3227        if (mInputContentType == null) mInputContentType = new InputContentType();
3228        mInputContentType.extras = new Bundle();
3229        getResources().parseBundleExtras(parser, mInputContentType.extras);
3230    }
3231
3232    /**
3233     * Retrieve the input extras currently associated with the text view, which
3234     * can be viewed as well as modified.
3235     *
3236     * @param create If true, the extras will be created if they don't already
3237     * exist.  Otherwise, null will be returned if none have been created.
3238     * @see #setInputExtras(int)
3239     * @see EditorInfo#extras
3240     * @attr ref android.R.styleable#TextView_editorExtras
3241     */
3242    public Bundle getInputExtras(boolean create) {
3243        if (mInputContentType == null) {
3244            if (!create) return null;
3245            mInputContentType = new InputContentType();
3246        }
3247        if (mInputContentType.extras == null) {
3248            if (!create) return null;
3249            mInputContentType.extras = new Bundle();
3250        }
3251        return mInputContentType.extras;
3252    }
3253
3254    /**
3255     * Returns the error message that was set to be displayed with
3256     * {@link #setError}, or <code>null</code> if no error was set
3257     * or if it the error was cleared by the widget after user input.
3258     */
3259    public CharSequence getError() {
3260        return mError;
3261    }
3262
3263    /**
3264     * Sets the right-hand compound drawable of the TextView to the "error"
3265     * icon and sets an error message that will be displayed in a popup when
3266     * the TextView has focus.  The icon and error message will be reset to
3267     * null when any key events cause changes to the TextView's text.  If the
3268     * <code>error</code> is <code>null</code>, the error message and icon
3269     * will be cleared.
3270     */
3271    @android.view.RemotableViewMethod
3272    public void setError(CharSequence error) {
3273        if (error == null) {
3274            setError(null, null);
3275        } else {
3276            Drawable dr = getContext().getResources().
3277                getDrawable(com.android.internal.R.drawable.
3278                            indicator_input_error);
3279
3280            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3281            setError(error, dr);
3282        }
3283    }
3284
3285    /**
3286     * Sets the right-hand compound drawable of the TextView to the specified
3287     * icon and sets an error message that will be displayed in a popup when
3288     * the TextView has focus.  The icon and error message will be reset to
3289     * null when any key events cause changes to the TextView's text.  The
3290     * drawable must already have had {@link Drawable#setBounds} set on it.
3291     * If the <code>error</code> is <code>null</code>, the error message will
3292     * be cleared (and you should provide a <code>null</code> icon as well).
3293     */
3294    public void setError(CharSequence error, Drawable icon) {
3295        error = TextUtils.stringOrSpannedString(error);
3296
3297        mError = error;
3298        mErrorWasChanged = true;
3299        final Drawables dr = mDrawables;
3300        if (dr != null) {
3301            setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
3302                                 icon, dr.mDrawableBottom);
3303        } else {
3304            setCompoundDrawables(null, null, icon, null);
3305        }
3306
3307        if (error == null) {
3308            if (mPopup != null) {
3309                if (mPopup.isShowing()) {
3310                    mPopup.dismiss();
3311                }
3312
3313                mPopup = null;
3314            }
3315        } else {
3316            if (isFocused()) {
3317                showError();
3318            }
3319        }
3320    }
3321
3322    private void showError() {
3323        if (getWindowToken() == null) {
3324            mShowErrorAfterAttach = true;
3325            return;
3326        }
3327
3328        if (mPopup == null) {
3329            LayoutInflater inflater = LayoutInflater.from(getContext());
3330            final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
3331                    null);
3332
3333            final float scale = getResources().getDisplayMetrics().density;
3334            mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f),
3335                    (int) (50 * scale + 0.5f));
3336            mPopup.setFocusable(false);
3337            // The user is entering text, so the input method is needed.  We
3338            // don't want the popup to be displayed on top of it.
3339            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
3340        }
3341
3342        TextView tv = (TextView) mPopup.getContentView();
3343        chooseSize(mPopup, mError, tv);
3344        tv.setText(mError);
3345
3346        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
3347        mPopup.fixDirection(mPopup.isAboveAnchor());
3348    }
3349
3350    private static class ErrorPopup extends PopupWindow {
3351        private boolean mAbove = false;
3352        private final TextView mView;
3353
3354        ErrorPopup(TextView v, int width, int height) {
3355            super(v, width, height);
3356            mView = v;
3357        }
3358
3359        void fixDirection(boolean above) {
3360            mAbove = above;
3361
3362            if (above) {
3363                mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
3364            } else {
3365                mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
3366            }
3367        }
3368
3369        @Override
3370        public void update(int x, int y, int w, int h, boolean force) {
3371            super.update(x, y, w, h, force);
3372
3373            boolean above = isAboveAnchor();
3374            if (above != mAbove) {
3375                fixDirection(above);
3376            }
3377        }
3378    }
3379
3380    /**
3381     * Returns the Y offset to make the pointy top of the error point
3382     * at the middle of the error icon.
3383     */
3384    private int getErrorX() {
3385        /*
3386         * The "25" is the distance between the point and the right edge
3387         * of the background
3388         */
3389        final float scale = getResources().getDisplayMetrics().density;
3390
3391        final Drawables dr = mDrawables;
3392        return getWidth() - mPopup.getWidth()
3393                - getPaddingRight()
3394                - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
3395    }
3396
3397    /**
3398     * Returns the Y offset to make the pointy top of the error point
3399     * at the bottom of the error icon.
3400     */
3401    private int getErrorY() {
3402        /*
3403         * Compound, not extended, because the icon is not clipped
3404         * if the text height is smaller.
3405         */
3406        int vspace = mBottom - mTop -
3407                     getCompoundPaddingBottom() - getCompoundPaddingTop();
3408
3409        final Drawables dr = mDrawables;
3410        int icontop = getCompoundPaddingTop()
3411                + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
3412
3413        /*
3414         * The "2" is the distance between the point and the top edge
3415         * of the background.
3416         */
3417
3418        return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
3419                - getHeight() - 2;
3420    }
3421
3422    private void hideError() {
3423        if (mPopup != null) {
3424            if (mPopup.isShowing()) {
3425                mPopup.dismiss();
3426            }
3427        }
3428
3429        mShowErrorAfterAttach = false;
3430    }
3431
3432    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
3433        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
3434        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
3435
3436        /*
3437         * Figure out how big the text would be if we laid it out to the
3438         * full width of this view minus the border.
3439         */
3440        int cap = getWidth() - wid;
3441        if (cap < 0) {
3442            cap = 200; // We must not be measured yet -- setFrame() will fix it.
3443        }
3444
3445        Layout l = new StaticLayout(text, tv.getPaint(), cap,
3446                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
3447        float max = 0;
3448        for (int i = 0; i < l.getLineCount(); i++) {
3449            max = Math.max(max, l.getLineWidth(i));
3450        }
3451
3452        /*
3453         * Now set the popup size to be big enough for the text plus the border.
3454         */
3455        pop.setWidth(wid + (int) Math.ceil(max));
3456        pop.setHeight(ht + l.getHeight());
3457    }
3458
3459
3460    @Override
3461    protected boolean setFrame(int l, int t, int r, int b) {
3462        boolean result = super.setFrame(l, t, r, b);
3463
3464        if (mPopup != null) {
3465            TextView tv = (TextView) mPopup.getContentView();
3466            chooseSize(mPopup, mError, tv);
3467            mPopup.update(this, getErrorX(), getErrorY(),
3468                          mPopup.getWidth(), mPopup.getHeight());
3469        }
3470
3471        restartMarqueeIfNeeded();
3472
3473        return result;
3474    }
3475
3476    private void restartMarqueeIfNeeded() {
3477        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3478            mRestartMarquee = false;
3479            startMarquee();
3480        }
3481    }
3482
3483    /**
3484     * Sets the list of input filters that will be used if the buffer is
3485     * Editable.  Has no effect otherwise.
3486     *
3487     * @attr ref android.R.styleable#TextView_maxLength
3488     */
3489    public void setFilters(InputFilter[] filters) {
3490        if (filters == null) {
3491            throw new IllegalArgumentException();
3492        }
3493
3494        mFilters = filters;
3495
3496        if (mText instanceof Editable) {
3497            setFilters((Editable) mText, filters);
3498        }
3499    }
3500
3501    /**
3502     * Sets the list of input filters on the specified Editable,
3503     * and includes mInput in the list if it is an InputFilter.
3504     */
3505    private void setFilters(Editable e, InputFilter[] filters) {
3506        if (mInput instanceof InputFilter) {
3507            InputFilter[] nf = new InputFilter[filters.length + 1];
3508
3509            System.arraycopy(filters, 0, nf, 0, filters.length);
3510            nf[filters.length] = (InputFilter) mInput;
3511
3512            e.setFilters(nf);
3513        } else {
3514            e.setFilters(filters);
3515        }
3516    }
3517
3518    /**
3519     * Returns the current list of input filters.
3520     */
3521    public InputFilter[] getFilters() {
3522        return mFilters;
3523    }
3524
3525    /////////////////////////////////////////////////////////////////////////
3526
3527    private int getVerticalOffset(boolean forceNormal) {
3528        int voffset = 0;
3529        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3530
3531        Layout l = mLayout;
3532        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3533            l = mHintLayout;
3534        }
3535
3536        if (gravity != Gravity.TOP) {
3537            int boxht;
3538
3539            if (l == mHintLayout) {
3540                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3541                        getCompoundPaddingBottom();
3542            } else {
3543                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3544                        getExtendedPaddingBottom();
3545            }
3546            int textht = l.getHeight();
3547
3548            if (textht < boxht) {
3549                if (gravity == Gravity.BOTTOM)
3550                    voffset = boxht - textht;
3551                else // (gravity == Gravity.CENTER_VERTICAL)
3552                    voffset = (boxht - textht) >> 1;
3553            }
3554        }
3555        return voffset;
3556    }
3557
3558    private int getBottomVerticalOffset(boolean forceNormal) {
3559        int voffset = 0;
3560        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3561
3562        Layout l = mLayout;
3563        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3564            l = mHintLayout;
3565        }
3566
3567        if (gravity != Gravity.BOTTOM) {
3568            int boxht;
3569
3570            if (l == mHintLayout) {
3571                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3572                        getCompoundPaddingBottom();
3573            } else {
3574                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3575                        getExtendedPaddingBottom();
3576            }
3577            int textht = l.getHeight();
3578
3579            if (textht < boxht) {
3580                if (gravity == Gravity.TOP)
3581                    voffset = boxht - textht;
3582                else // (gravity == Gravity.CENTER_VERTICAL)
3583                    voffset = (boxht - textht) >> 1;
3584            }
3585        }
3586        return voffset;
3587    }
3588
3589    private void invalidateCursorPath() {
3590        if (mHighlightPathBogus) {
3591            invalidateCursor();
3592        } else {
3593            synchronized (sTempRect) {
3594                /*
3595                 * The reason for this concern about the thickness of the
3596                 * cursor and doing the floor/ceil on the coordinates is that
3597                 * some EditTexts (notably textfields in the Browser) have
3598                 * anti-aliased text where not all the characters are
3599                 * necessarily at integer-multiple locations.  This should
3600                 * make sure the entire cursor gets invalidated instead of
3601                 * sometimes missing half a pixel.
3602                 */
3603
3604                float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
3605                if (thick < 1.0f) {
3606                    thick = 1.0f;
3607                }
3608
3609                thick /= 2;
3610
3611                mHighlightPath.computeBounds(sTempRect, false);
3612
3613                int left = getCompoundPaddingLeft();
3614                int top = getExtendedPaddingTop() + getVerticalOffset(true);
3615
3616                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
3617                           (int) FloatMath.floor(top + sTempRect.top - thick),
3618                           (int) FloatMath.ceil(left + sTempRect.right + thick),
3619                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
3620            }
3621        }
3622    }
3623
3624    private void invalidateCursor() {
3625        int where = getSelectionEnd();
3626
3627        invalidateCursor(where, where, where);
3628    }
3629
3630    private void invalidateCursor(int a, int b, int c) {
3631        if (mLayout == null) {
3632            invalidate();
3633        } else {
3634            if (a >= 0 || b >= 0 || c >= 0) {
3635                int first = Math.min(Math.min(a, b), c);
3636                int last = Math.max(Math.max(a, b), c);
3637
3638                int line = mLayout.getLineForOffset(first);
3639                int top = mLayout.getLineTop(line);
3640
3641                // This is ridiculous, but the descent from the line above
3642                // can hang down into the line we really want to redraw,
3643                // so we have to invalidate part of the line above to make
3644                // sure everything that needs to be redrawn really is.
3645                // (But not the whole line above, because that would cause
3646                // the same problem with the descenders on the line above it!)
3647                if (line > 0) {
3648                    top -= mLayout.getLineDescent(line - 1);
3649                }
3650
3651                int line2;
3652
3653                if (first == last)
3654                    line2 = line;
3655                else
3656                    line2 = mLayout.getLineForOffset(last);
3657
3658                int bottom = mLayout.getLineTop(line2 + 1);
3659                int voffset = getVerticalOffset(true);
3660
3661                int left = getCompoundPaddingLeft() + mScrollX;
3662                invalidate(left, top + voffset + getExtendedPaddingTop(),
3663                           left + getWidth() - getCompoundPaddingLeft() -
3664                           getCompoundPaddingRight(),
3665                           bottom + voffset + getExtendedPaddingTop());
3666            }
3667        }
3668    }
3669
3670    private void registerForPreDraw() {
3671        final ViewTreeObserver observer = getViewTreeObserver();
3672        if (observer == null) {
3673            return;
3674        }
3675
3676        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
3677            observer.addOnPreDrawListener(this);
3678            mPreDrawState = PREDRAW_PENDING;
3679        } else if (mPreDrawState == PREDRAW_DONE) {
3680            mPreDrawState = PREDRAW_PENDING;
3681        }
3682
3683        // else state is PREDRAW_PENDING, so keep waiting.
3684    }
3685
3686    /**
3687     * {@inheritDoc}
3688     */
3689    public boolean onPreDraw() {
3690        if (mPreDrawState != PREDRAW_PENDING) {
3691            return true;
3692        }
3693
3694        if (mLayout == null) {
3695            assumeLayout();
3696        }
3697
3698        boolean changed = false;
3699
3700        SelectionModifierCursorController selectionController = null;
3701        if (mSelectionModifierCursorController != null) {
3702            selectionController = (SelectionModifierCursorController)
3703                mSelectionModifierCursorController;
3704        }
3705
3706
3707        if (mMovement != null) {
3708            /* This code also provides auto-scrolling when a cursor is moved using a
3709             * CursorController (insertion point or selection limits).
3710             * For selection, ensure start or end is visible depending on controller's state.
3711             */
3712            int curs = getSelectionEnd();
3713            if (selectionController != null && selectionController.isSelectionStartDragged()) {
3714                curs = getSelectionStart();
3715            }
3716
3717            /*
3718             * TODO: This should really only keep the end in view if
3719             * it already was before the text changed.  I'm not sure
3720             * of a good way to tell from here if it was.
3721             */
3722            if (curs < 0 &&
3723                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3724                curs = mText.length();
3725            }
3726
3727            if (curs >= 0) {
3728                changed = bringPointIntoView(curs);
3729            }
3730        } else {
3731            changed = bringTextIntoView();
3732        }
3733
3734        // This has to be checked here since:
3735        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
3736        //   a screen rotation) since layout is not yet initialized at that point.
3737        // - ExtractEditText does not call onFocus when it is displayed. Fixing this issue would
3738        //   allow to test for hasSelection in onFocusChanged, which would trigger a
3739        //   startTextSelectionMode here. TODO
3740        if (selectionController != null && hasSelection()) {
3741            startTextSelectionMode();
3742        }
3743
3744        mPreDrawState = PREDRAW_DONE;
3745        return !changed;
3746    }
3747
3748    @Override
3749    protected void onAttachedToWindow() {
3750        super.onAttachedToWindow();
3751
3752        mTemporaryDetach = false;
3753
3754        if (mShowErrorAfterAttach) {
3755            showError();
3756            mShowErrorAfterAttach = false;
3757        }
3758
3759        final ViewTreeObserver observer = getViewTreeObserver();
3760        if (observer != null) {
3761            if (mInsertionPointCursorController != null) {
3762                observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
3763            }
3764            if (mSelectionModifierCursorController != null) {
3765                observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
3766            }
3767        }
3768    }
3769
3770    @Override
3771    protected void onDetachedFromWindow() {
3772        super.onDetachedFromWindow();
3773
3774        final ViewTreeObserver observer = getViewTreeObserver();
3775        if (observer != null) {
3776            if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
3777                observer.removeOnPreDrawListener(this);
3778                mPreDrawState = PREDRAW_NOT_REGISTERED;
3779            }
3780            if (mInsertionPointCursorController != null) {
3781                observer.removeOnTouchModeChangeListener(mInsertionPointCursorController);
3782            }
3783            if (mSelectionModifierCursorController != null) {
3784                observer.removeOnTouchModeChangeListener(mSelectionModifierCursorController);
3785            }
3786        }
3787
3788        if (mError != null) {
3789            hideError();
3790        }
3791
3792        hideControllers();
3793    }
3794
3795    @Override
3796    protected boolean isPaddingOffsetRequired() {
3797        return mShadowRadius != 0 || mDrawables != null;
3798    }
3799
3800    @Override
3801    protected int getLeftPaddingOffset() {
3802        return getCompoundPaddingLeft() - mPaddingLeft +
3803                (int) Math.min(0, mShadowDx - mShadowRadius);
3804    }
3805
3806    @Override
3807    protected int getTopPaddingOffset() {
3808        return (int) Math.min(0, mShadowDy - mShadowRadius);
3809    }
3810
3811    @Override
3812    protected int getBottomPaddingOffset() {
3813        return (int) Math.max(0, mShadowDy + mShadowRadius);
3814    }
3815
3816    @Override
3817    protected int getRightPaddingOffset() {
3818        return -(getCompoundPaddingRight() - mPaddingRight) +
3819                (int) Math.max(0, mShadowDx + mShadowRadius);
3820    }
3821
3822    @Override
3823    protected boolean verifyDrawable(Drawable who) {
3824        final boolean verified = super.verifyDrawable(who);
3825        if (!verified && mDrawables != null) {
3826            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
3827                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
3828        }
3829        return verified;
3830    }
3831
3832    @Override
3833    public void invalidateDrawable(Drawable drawable) {
3834        if (verifyDrawable(drawable)) {
3835            final Rect dirty = drawable.getBounds();
3836            int scrollX = mScrollX;
3837            int scrollY = mScrollY;
3838
3839            // IMPORTANT: The coordinates below are based on the coordinates computed
3840            // for each compound drawable in onDraw(). Make sure to update each section
3841            // accordingly.
3842            final TextView.Drawables drawables = mDrawables;
3843            if (drawables != null) {
3844                if (drawable == drawables.mDrawableLeft) {
3845                    final int compoundPaddingTop = getCompoundPaddingTop();
3846                    final int compoundPaddingBottom = getCompoundPaddingBottom();
3847                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
3848
3849                    scrollX += mPaddingLeft;
3850                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
3851                } else if (drawable == drawables.mDrawableRight) {
3852                    final int compoundPaddingTop = getCompoundPaddingTop();
3853                    final int compoundPaddingBottom = getCompoundPaddingBottom();
3854                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
3855
3856                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
3857                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
3858                } else if (drawable == drawables.mDrawableTop) {
3859                    final int compoundPaddingLeft = getCompoundPaddingLeft();
3860                    final int compoundPaddingRight = getCompoundPaddingRight();
3861                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
3862
3863                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
3864                    scrollY += mPaddingTop;
3865                } else if (drawable == drawables.mDrawableBottom) {
3866                    final int compoundPaddingLeft = getCompoundPaddingLeft();
3867                    final int compoundPaddingRight = getCompoundPaddingRight();
3868                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
3869
3870                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
3871                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
3872                }
3873            }
3874
3875            invalidate(dirty.left + scrollX, dirty.top + scrollY,
3876                    dirty.right + scrollX, dirty.bottom + scrollY);
3877        }
3878    }
3879
3880    @Override
3881    protected void onDraw(Canvas canvas) {
3882        restartMarqueeIfNeeded();
3883
3884        // Draw the background for this view
3885        super.onDraw(canvas);
3886
3887        final int compoundPaddingLeft = getCompoundPaddingLeft();
3888        final int compoundPaddingTop = getCompoundPaddingTop();
3889        final int compoundPaddingRight = getCompoundPaddingRight();
3890        final int compoundPaddingBottom = getCompoundPaddingBottom();
3891        final int scrollX = mScrollX;
3892        final int scrollY = mScrollY;
3893        final int right = mRight;
3894        final int left = mLeft;
3895        final int bottom = mBottom;
3896        final int top = mTop;
3897
3898        final Drawables dr = mDrawables;
3899        if (dr != null) {
3900            /*
3901             * Compound, not extended, because the icon is not clipped
3902             * if the text height is smaller.
3903             */
3904
3905            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
3906            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
3907
3908            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3909            // Make sure to update invalidateDrawable() when changing this code.
3910            if (dr.mDrawableLeft != null) {
3911                canvas.save();
3912                canvas.translate(scrollX + mPaddingLeft,
3913                                 scrollY + compoundPaddingTop +
3914                                 (vspace - dr.mDrawableHeightLeft) / 2);
3915                dr.mDrawableLeft.draw(canvas);
3916                canvas.restore();
3917            }
3918
3919            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3920            // Make sure to update invalidateDrawable() when changing this code.
3921            if (dr.mDrawableRight != null) {
3922                canvas.save();
3923                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
3924                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
3925                dr.mDrawableRight.draw(canvas);
3926                canvas.restore();
3927            }
3928
3929            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3930            // Make sure to update invalidateDrawable() when changing this code.
3931            if (dr.mDrawableTop != null) {
3932                canvas.save();
3933                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
3934                        scrollY + mPaddingTop);
3935                dr.mDrawableTop.draw(canvas);
3936                canvas.restore();
3937            }
3938
3939            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3940            // Make sure to update invalidateDrawable() when changing this code.
3941            if (dr.mDrawableBottom != null) {
3942                canvas.save();
3943                canvas.translate(scrollX + compoundPaddingLeft +
3944                        (hspace - dr.mDrawableWidthBottom) / 2,
3945                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
3946                dr.mDrawableBottom.draw(canvas);
3947                canvas.restore();
3948            }
3949        }
3950
3951        if (mPreDrawState == PREDRAW_DONE) {
3952            final ViewTreeObserver observer = getViewTreeObserver();
3953            if (observer != null) {
3954                observer.removeOnPreDrawListener(this);
3955                mPreDrawState = PREDRAW_NOT_REGISTERED;
3956            }
3957        }
3958
3959        int color = mCurTextColor;
3960
3961        if (mLayout == null) {
3962            assumeLayout();
3963        }
3964
3965        Layout layout = mLayout;
3966        int cursorcolor = color;
3967
3968        if (mHint != null && mText.length() == 0) {
3969            if (mHintTextColor != null) {
3970                color = mCurHintTextColor;
3971            }
3972
3973            layout = mHintLayout;
3974        }
3975
3976        mTextPaint.setColor(color);
3977        mTextPaint.drawableState = getDrawableState();
3978
3979        canvas.save();
3980        /*  Would be faster if we didn't have to do this. Can we chop the
3981            (displayable) text so that we don't need to do this ever?
3982        */
3983
3984        int extendedPaddingTop = getExtendedPaddingTop();
3985        int extendedPaddingBottom = getExtendedPaddingBottom();
3986
3987        float clipLeft = compoundPaddingLeft + scrollX;
3988        float clipTop = extendedPaddingTop + scrollY;
3989        float clipRight = right - left - compoundPaddingRight + scrollX;
3990        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
3991
3992        if (mShadowRadius != 0) {
3993            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
3994            clipRight += Math.max(0, mShadowDx + mShadowRadius);
3995
3996            clipTop += Math.min(0, mShadowDy - mShadowRadius);
3997            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
3998        }
3999
4000        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4001
4002        int voffsetText = 0;
4003        int voffsetCursor = 0;
4004
4005        // translate in by our padding
4006        {
4007            /* shortcircuit calling getVerticaOffset() */
4008            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4009                voffsetText = getVerticalOffset(false);
4010                voffsetCursor = getVerticalOffset(true);
4011            }
4012            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
4013        }
4014
4015        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4016            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
4017                    (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
4018                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
4019                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
4020            }
4021
4022            if (mMarquee != null && mMarquee.isRunning()) {
4023                canvas.translate(-mMarquee.mScroll, 0.0f);
4024            }
4025        }
4026
4027        Path highlight = null;
4028        int selStart = -1, selEnd = -1;
4029
4030        //  If there is no movement method, then there can be no selection.
4031        //  Check that first and attempt to skip everything having to do with
4032        //  the cursor.
4033        //  XXX This is not strictly true -- a program could set the
4034        //  selection manually if it really wanted to.
4035        if (mMovement != null && (isFocused() || isPressed())) {
4036            selStart = getSelectionStart();
4037            selEnd = getSelectionEnd();
4038
4039            if (mCursorVisible && selStart >= 0 && isEnabled()) {
4040                if (mHighlightPath == null)
4041                    mHighlightPath = new Path();
4042
4043                if (selStart == selEnd) {
4044                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
4045                        if (mHighlightPathBogus) {
4046                            mHighlightPath.reset();
4047                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
4048                            mHighlightPathBogus = false;
4049                        }
4050
4051                        // XXX should pass to skin instead of drawing directly
4052                        mHighlightPaint.setColor(cursorcolor);
4053                        mHighlightPaint.setStyle(Paint.Style.STROKE);
4054
4055                        highlight = mHighlightPath;
4056                    }
4057                } else {
4058                    if (mHighlightPathBogus) {
4059                        mHighlightPath.reset();
4060                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4061                        mHighlightPathBogus = false;
4062                    }
4063
4064                    // XXX should pass to skin instead of drawing directly
4065                    mHighlightPaint.setColor(mHighlightColor);
4066                    mHighlightPaint.setStyle(Paint.Style.FILL);
4067
4068                    highlight = mHighlightPath;
4069                }
4070            }
4071        }
4072
4073        /*  Comment out until we decide what to do about animations
4074        boolean isLinearTextOn = false;
4075        if (currentTransformation != null) {
4076            isLinearTextOn = mTextPaint.isLinearTextOn();
4077            Matrix m = currentTransformation.getMatrix();
4078            if (!m.isIdentity()) {
4079                // mTextPaint.setLinearTextOn(true);
4080            }
4081        }
4082        */
4083
4084        final InputMethodState ims = mInputMethodState;
4085        if (ims != null && ims.mBatchEditNesting == 0) {
4086            InputMethodManager imm = InputMethodManager.peekInstance();
4087            if (imm != null) {
4088                if (imm.isActive(this)) {
4089                    boolean reported = false;
4090                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
4091                        // We are in extract mode and the content has changed
4092                        // in some way... just report complete new text to the
4093                        // input method.
4094                        reported = reportExtractedText();
4095                    }
4096                    if (!reported && highlight != null) {
4097                        int candStart = -1;
4098                        int candEnd = -1;
4099                        if (mText instanceof Spannable) {
4100                            Spannable sp = (Spannable)mText;
4101                            candStart = EditableInputConnection.getComposingSpanStart(sp);
4102                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4103                        }
4104                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4105                    }
4106                }
4107
4108                if (imm.isWatchingCursor(this) && highlight != null) {
4109                    highlight.computeBounds(ims.mTmpRectF, true);
4110                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4111
4112                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
4113                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4114
4115                    ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
4116
4117                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4118                            (int)(ims.mTmpRectF.top + 0.5),
4119                            (int)(ims.mTmpRectF.right + 0.5),
4120                            (int)(ims.mTmpRectF.bottom + 0.5));
4121
4122                    imm.updateCursor(this,
4123                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4124                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4125                }
4126            }
4127        }
4128
4129        layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
4130
4131        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
4132            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
4133            layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
4134        }
4135
4136        /*  Comment out until we decide what to do about animations
4137        if (currentTransformation != null) {
4138            mTextPaint.setLinearTextOn(isLinearTextOn);
4139        }
4140        */
4141
4142        canvas.restore();
4143
4144        if (mInsertionPointCursorController != null &&
4145                mInsertionPointCursorController.isShowing()) {
4146            mInsertionPointCursorController.updatePosition();
4147        }
4148        if (mSelectionModifierCursorController != null &&
4149                mSelectionModifierCursorController.isShowing()) {
4150            mSelectionModifierCursorController.updatePosition();
4151        }
4152    }
4153
4154    @Override
4155    public void getFocusedRect(Rect r) {
4156        if (mLayout == null) {
4157            super.getFocusedRect(r);
4158            return;
4159        }
4160
4161        int sel = getSelectionEnd();
4162        if (sel < 0) {
4163            super.getFocusedRect(r);
4164            return;
4165        }
4166
4167        int line = mLayout.getLineForOffset(sel);
4168        r.top = mLayout.getLineTop(line);
4169        r.bottom = mLayout.getLineBottom(line);
4170
4171        r.left = (int) mLayout.getPrimaryHorizontal(sel);
4172        r.right = r.left + 1;
4173
4174        // Adjust for padding and gravity.
4175        int paddingLeft = getCompoundPaddingLeft();
4176        int paddingTop = getExtendedPaddingTop();
4177        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4178            paddingTop += getVerticalOffset(false);
4179        }
4180        r.offset(paddingLeft, paddingTop);
4181    }
4182
4183    /**
4184     * Return the number of lines of text, or 0 if the internal Layout has not
4185     * been built.
4186     */
4187    public int getLineCount() {
4188        return mLayout != null ? mLayout.getLineCount() : 0;
4189    }
4190
4191    /**
4192     * Return the baseline for the specified line (0...getLineCount() - 1)
4193     * If bounds is not null, return the top, left, right, bottom extents
4194     * of the specified line in it. If the internal Layout has not been built,
4195     * return 0 and set bounds to (0, 0, 0, 0)
4196     * @param line which line to examine (0..getLineCount() - 1)
4197     * @param bounds Optional. If not null, it returns the extent of the line
4198     * @return the Y-coordinate of the baseline
4199     */
4200    public int getLineBounds(int line, Rect bounds) {
4201        if (mLayout == null) {
4202            if (bounds != null) {
4203                bounds.set(0, 0, 0, 0);
4204            }
4205            return 0;
4206        }
4207        else {
4208            int baseline = mLayout.getLineBounds(line, bounds);
4209
4210            int voffset = getExtendedPaddingTop();
4211            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4212                voffset += getVerticalOffset(true);
4213            }
4214            if (bounds != null) {
4215                bounds.offset(getCompoundPaddingLeft(), voffset);
4216            }
4217            return baseline + voffset;
4218        }
4219    }
4220
4221    @Override
4222    public int getBaseline() {
4223        if (mLayout == null) {
4224            return super.getBaseline();
4225        }
4226
4227        int voffset = 0;
4228        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4229            voffset = getVerticalOffset(true);
4230        }
4231
4232        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
4233    }
4234
4235    @Override
4236    public boolean onKeyDown(int keyCode, KeyEvent event) {
4237        int which = doKeyDown(keyCode, event, null);
4238        if (which == 0) {
4239            // Go through default dispatching.
4240            return super.onKeyDown(keyCode, event);
4241        }
4242
4243        return true;
4244    }
4245
4246    @Override
4247    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4248        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
4249
4250        int which = doKeyDown(keyCode, down, event);
4251        if (which == 0) {
4252            // Go through default dispatching.
4253            return super.onKeyMultiple(keyCode, repeatCount, event);
4254        }
4255        if (which == -1) {
4256            // Consumed the whole thing.
4257            return true;
4258        }
4259
4260        repeatCount--;
4261
4262        // We are going to dispatch the remaining events to either the input
4263        // or movement method.  To do this, we will just send a repeated stream
4264        // of down and up events until we have done the complete repeatCount.
4265        // It would be nice if those interfaces had an onKeyMultiple() method,
4266        // but adding that is a more complicated change.
4267        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
4268        if (which == 1) {
4269            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4270            while (--repeatCount > 0) {
4271                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
4272                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4273            }
4274            if (mError != null && !mErrorWasChanged) {
4275                setError(null, null);
4276            }
4277
4278        } else if (which == 2) {
4279            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4280            while (--repeatCount > 0) {
4281                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
4282                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4283            }
4284        }
4285
4286        return true;
4287    }
4288
4289    /**
4290     * Returns true if pressing ENTER in this field advances focus instead
4291     * of inserting the character.  This is true mostly in single-line fields,
4292     * but also in mail addresses and subjects which will display on multiple
4293     * lines but where it doesn't make sense to insert newlines.
4294     */
4295    private boolean shouldAdvanceFocusOnEnter() {
4296        if (mInput == null) {
4297            return false;
4298        }
4299
4300        if (mSingleLine) {
4301            return true;
4302        }
4303
4304        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4305            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4306
4307            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
4308                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
4309                return true;
4310            }
4311        }
4312
4313        return false;
4314    }
4315
4316    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
4317        if (!isEnabled()) {
4318            return 0;
4319        }
4320
4321        switch (keyCode) {
4322            case KeyEvent.KEYCODE_ENTER:
4323                // If ALT modifier is held, then we always insert a
4324                // newline character.
4325                if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
4326
4327                    // When mInputContentType is set, we know that we are
4328                    // running in a "modern" cupcake environment, so don't need
4329                    // to worry about the application trying to capture
4330                    // enter key events.
4331                    if (mInputContentType != null) {
4332
4333                        // If there is an action listener, given them a
4334                        // chance to consume the event.
4335                        if (mInputContentType.onEditorActionListener != null &&
4336                                mInputContentType.onEditorActionListener.onEditorAction(
4337                                this, EditorInfo.IME_NULL, event)) {
4338                            mInputContentType.enterDown = true;
4339                            // We are consuming the enter key for them.
4340                            return -1;
4341                        }
4342                    }
4343
4344                    // If our editor should move focus when enter is pressed, or
4345                    // this is a generated event from an IME action button, then
4346                    // don't let it be inserted into the text.
4347                    if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4348                            || shouldAdvanceFocusOnEnter()) {
4349                        return -1;
4350                    }
4351                }
4352                break;
4353
4354            case KeyEvent.KEYCODE_DPAD_CENTER:
4355                if (shouldAdvanceFocusOnEnter()) {
4356                    return 0;
4357                }
4358                break;
4359
4360                // Has to be done on key down (and not on key up) to correctly be intercepted.
4361            case KeyEvent.KEYCODE_BACK:
4362                if (mIsInTextSelectionMode) {
4363                    stopTextSelectionMode();
4364                    return -1;
4365                }
4366                break;
4367        }
4368
4369        if (mInput != null) {
4370            /*
4371             * Keep track of what the error was before doing the input
4372             * so that if an input filter changed the error, we leave
4373             * that error showing.  Otherwise, we take down whatever
4374             * error was showing when the user types something.
4375             */
4376            mErrorWasChanged = false;
4377
4378            boolean doDown = true;
4379            if (otherEvent != null) {
4380                try {
4381                    beginBatchEdit();
4382                    boolean handled = mInput.onKeyOther(this, (Editable) mText,
4383                            otherEvent);
4384                    if (mError != null && !mErrorWasChanged) {
4385                        setError(null, null);
4386                    }
4387                    doDown = false;
4388                    if (handled) {
4389                        return -1;
4390                    }
4391                } catch (AbstractMethodError e) {
4392                    // onKeyOther was added after 1.0, so if it isn't
4393                    // implemented we need to try to dispatch as a regular down.
4394                } finally {
4395                    endBatchEdit();
4396                }
4397            }
4398
4399            if (doDown) {
4400                beginBatchEdit();
4401                if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
4402                    endBatchEdit();
4403                    if (mError != null && !mErrorWasChanged) {
4404                        setError(null, null);
4405                    }
4406                    return 1;
4407                }
4408                endBatchEdit();
4409            }
4410        }
4411
4412        // bug 650865: sometimes we get a key event before a layout.
4413        // don't try to move around if we don't know the layout.
4414
4415        if (mMovement != null && mLayout != null) {
4416            boolean doDown = true;
4417            if (otherEvent != null) {
4418                try {
4419                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
4420                            otherEvent);
4421                    doDown = false;
4422                    if (handled) {
4423                        return -1;
4424                    }
4425                } catch (AbstractMethodError e) {
4426                    // onKeyOther was added after 1.0, so if it isn't
4427                    // implemented we need to try to dispatch as a regular down.
4428                }
4429            }
4430            if (doDown) {
4431                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
4432                    return 2;
4433            }
4434        }
4435
4436        return 0;
4437    }
4438
4439    @Override
4440    public boolean onKeyUp(int keyCode, KeyEvent event) {
4441        if (!isEnabled()) {
4442            return super.onKeyUp(keyCode, event);
4443        }
4444
4445        hideControllers();
4446
4447        switch (keyCode) {
4448            case KeyEvent.KEYCODE_DPAD_CENTER:
4449                /*
4450                 * If there is a click listener, just call through to
4451                 * super, which will invoke it.
4452                 *
4453                 * If there isn't a click listener, try to show the soft
4454                 * input method.  (It will also
4455                 * call performClick(), but that won't do anything in
4456                 * this case.)
4457                 */
4458                if (mOnClickListener == null) {
4459                    if (mMovement != null && mText instanceof Editable
4460                            && mLayout != null && onCheckIsTextEditor()) {
4461                        InputMethodManager imm = (InputMethodManager)
4462                                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
4463                        imm.showSoftInput(this, 0);
4464                    }
4465                }
4466                return super.onKeyUp(keyCode, event);
4467
4468            case KeyEvent.KEYCODE_ENTER:
4469                if (mInputContentType != null
4470                        && mInputContentType.onEditorActionListener != null
4471                        && mInputContentType.enterDown) {
4472                    mInputContentType.enterDown = false;
4473                    if (mInputContentType.onEditorActionListener.onEditorAction(
4474                            this, EditorInfo.IME_NULL, event)) {
4475                        return true;
4476                    }
4477                }
4478
4479                if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4480                        || shouldAdvanceFocusOnEnter()) {
4481                    /*
4482                     * If there is a click listener, just call through to
4483                     * super, which will invoke it.
4484                     *
4485                     * If there isn't a click listener, try to advance focus,
4486                     * but still call through to super, which will reset the
4487                     * pressed state and longpress state.  (It will also
4488                     * call performClick(), but that won't do anything in
4489                     * this case.)
4490                     */
4491                    if (mOnClickListener == null) {
4492                        View v = focusSearch(FOCUS_DOWN);
4493
4494                        if (v != null) {
4495                            if (!v.requestFocus(FOCUS_DOWN)) {
4496                                throw new IllegalStateException("focus search returned a view " +
4497                                        "that wasn't able to take focus!");
4498                            }
4499
4500                            /*
4501                             * Return true because we handled the key; super
4502                             * will return false because there was no click
4503                             * listener.
4504                             */
4505                            super.onKeyUp(keyCode, event);
4506                            return true;
4507                        } else if ((event.getFlags()
4508                                & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4509                            // No target for next focus, but make sure the IME
4510                            // if this came from it.
4511                            InputMethodManager imm = InputMethodManager.peekInstance();
4512                            if (imm != null) {
4513                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
4514                            }
4515                        }
4516                    }
4517
4518                    return super.onKeyUp(keyCode, event);
4519                }
4520                break;
4521        }
4522
4523        if (mInput != null)
4524            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
4525                return true;
4526
4527        if (mMovement != null && mLayout != null)
4528            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
4529                return true;
4530
4531        return super.onKeyUp(keyCode, event);
4532    }
4533
4534    @Override public boolean onCheckIsTextEditor() {
4535        return mInputType != EditorInfo.TYPE_NULL;
4536    }
4537
4538    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4539        if (onCheckIsTextEditor()) {
4540            if (mInputMethodState == null) {
4541                mInputMethodState = new InputMethodState();
4542            }
4543            outAttrs.inputType = mInputType;
4544            if (mInputContentType != null) {
4545                outAttrs.imeOptions = mInputContentType.imeOptions;
4546                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
4547                outAttrs.actionLabel = mInputContentType.imeActionLabel;
4548                outAttrs.actionId = mInputContentType.imeActionId;
4549                outAttrs.extras = mInputContentType.extras;
4550            } else {
4551                outAttrs.imeOptions = EditorInfo.IME_NULL;
4552            }
4553            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
4554                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
4555                if (focusSearch(FOCUS_DOWN) != null) {
4556                    // An action has not been set, but the enter key will move to
4557                    // the next focus, so set the action to that.
4558                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
4559                } else {
4560                    // An action has not been set, and there is no focus to move
4561                    // to, so let's just supply a "done" action.
4562                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
4563                }
4564                if (!shouldAdvanceFocusOnEnter()) {
4565                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4566                }
4567            }
4568            if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS
4569                    | InputType.TYPE_TEXT_FLAG_MULTI_LINE))
4570                    == (InputType.TYPE_CLASS_TEXT
4571                            | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) {
4572                // Multi-line text editors should always show an enter key.
4573                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4574            }
4575            outAttrs.hintText = mHint;
4576            if (mText instanceof Editable) {
4577                InputConnection ic = new EditableInputConnection(this);
4578                outAttrs.initialSelStart = getSelectionStart();
4579                outAttrs.initialSelEnd = getSelectionEnd();
4580                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
4581                return ic;
4582            }
4583        }
4584        return null;
4585    }
4586
4587    /**
4588     * If this TextView contains editable content, extract a portion of it
4589     * based on the information in <var>request</var> in to <var>outText</var>.
4590     * @return Returns true if the text was successfully extracted, else false.
4591     */
4592    public boolean extractText(ExtractedTextRequest request,
4593            ExtractedText outText) {
4594        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
4595                EXTRACT_UNKNOWN, outText);
4596    }
4597
4598    static final int EXTRACT_NOTHING = -2;
4599    static final int EXTRACT_UNKNOWN = -1;
4600
4601    boolean extractTextInternal(ExtractedTextRequest request,
4602            int partialStartOffset, int partialEndOffset, int delta,
4603            ExtractedText outText) {
4604        final CharSequence content = mText;
4605        if (content != null) {
4606            if (partialStartOffset != EXTRACT_NOTHING) {
4607                final int N = content.length();
4608                if (partialStartOffset < 0) {
4609                    outText.partialStartOffset = outText.partialEndOffset = -1;
4610                    partialStartOffset = 0;
4611                    partialEndOffset = N;
4612                } else {
4613                    // Now use the delta to determine the actual amount of text
4614                    // we need.
4615                    partialEndOffset += delta;
4616                    // Adjust offsets to ensure we contain full spans.
4617                    if (content instanceof Spanned) {
4618                        Spanned spanned = (Spanned)content;
4619                        Object[] spans = spanned.getSpans(partialStartOffset,
4620                                partialEndOffset, ParcelableSpan.class);
4621                        int i = spans.length;
4622                        while (i > 0) {
4623                            i--;
4624                            int j = spanned.getSpanStart(spans[i]);
4625                            if (j < partialStartOffset) partialStartOffset = j;
4626                            j = spanned.getSpanEnd(spans[i]);
4627                            if (j > partialEndOffset) partialEndOffset = j;
4628                        }
4629                    }
4630                    outText.partialStartOffset = partialStartOffset;
4631                    outText.partialEndOffset = partialEndOffset - delta;
4632
4633                    if (partialStartOffset > N) {
4634                        partialStartOffset = N;
4635                    } else if (partialStartOffset < 0) {
4636                        partialStartOffset = 0;
4637                    }
4638                    if (partialEndOffset > N) {
4639                        partialEndOffset = N;
4640                    } else if (partialEndOffset < 0) {
4641                        partialEndOffset = 0;
4642                    }
4643                }
4644                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
4645                    outText.text = content.subSequence(partialStartOffset,
4646                            partialEndOffset);
4647                } else {
4648                    outText.text = TextUtils.substring(content, partialStartOffset,
4649                            partialEndOffset);
4650                }
4651            } else {
4652                outText.partialStartOffset = 0;
4653                outText.partialEndOffset = 0;
4654                outText.text = "";
4655            }
4656            outText.flags = 0;
4657            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
4658                outText.flags |= ExtractedText.FLAG_SELECTING;
4659            }
4660            if (mSingleLine) {
4661                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
4662            }
4663            outText.startOffset = 0;
4664            outText.selectionStart = getSelectionStart();
4665            outText.selectionEnd = getSelectionEnd();
4666            return true;
4667        }
4668        return false;
4669    }
4670
4671    boolean reportExtractedText() {
4672        final InputMethodState ims = mInputMethodState;
4673        if (ims != null) {
4674            final boolean contentChanged = ims.mContentChanged;
4675            if (contentChanged || ims.mSelectionModeChanged) {
4676                ims.mContentChanged = false;
4677                ims.mSelectionModeChanged = false;
4678                final ExtractedTextRequest req = mInputMethodState.mExtracting;
4679                if (req != null) {
4680                    InputMethodManager imm = InputMethodManager.peekInstance();
4681                    if (imm != null) {
4682                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
4683                                + ims.mChangedStart + " end=" + ims.mChangedEnd
4684                                + " delta=" + ims.mChangedDelta);
4685                        if (ims.mChangedStart < 0 && !contentChanged) {
4686                            ims.mChangedStart = EXTRACT_NOTHING;
4687                        }
4688                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
4689                                ims.mChangedDelta, ims.mTmpExtracted)) {
4690                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
4691                                    + ims.mTmpExtracted.partialStartOffset
4692                                    + " end=" + ims.mTmpExtracted.partialEndOffset
4693                                    + ": " + ims.mTmpExtracted.text);
4694                            imm.updateExtractedText(this, req.token,
4695                                    mInputMethodState.mTmpExtracted);
4696                            ims.mChangedStart = EXTRACT_UNKNOWN;
4697                            ims.mChangedEnd = EXTRACT_UNKNOWN;
4698                            ims.mChangedDelta = 0;
4699                            ims.mContentChanged = false;
4700                            return true;
4701                        }
4702                    }
4703                }
4704            }
4705        }
4706        return false;
4707    }
4708
4709    /**
4710     * This is used to remove all style-impacting spans from text before new
4711     * extracted text is being replaced into it, so that we don't have any
4712     * lingering spans applied during the replace.
4713     */
4714    static void removeParcelableSpans(Spannable spannable, int start, int end) {
4715        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
4716        int i = spans.length;
4717        while (i > 0) {
4718            i--;
4719            spannable.removeSpan(spans[i]);
4720        }
4721    }
4722
4723    /**
4724     * Apply to this text view the given extracted text, as previously
4725     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
4726     */
4727    public void setExtractedText(ExtractedText text) {
4728        Editable content = getEditableText();
4729        if (text.text != null) {
4730            if (content == null) {
4731                setText(text.text, TextView.BufferType.EDITABLE);
4732            } else if (text.partialStartOffset < 0) {
4733                removeParcelableSpans(content, 0, content.length());
4734                content.replace(0, content.length(), text.text);
4735            } else {
4736                final int N = content.length();
4737                int start = text.partialStartOffset;
4738                if (start > N) start = N;
4739                int end = text.partialEndOffset;
4740                if (end > N) end = N;
4741                removeParcelableSpans(content, start, end);
4742                content.replace(start, end, text.text);
4743            }
4744        }
4745
4746        // Now set the selection position...  make sure it is in range, to
4747        // avoid crashes.  If this is a partial update, it is possible that
4748        // the underlying text may have changed, causing us problems here.
4749        // Also we just don't want to trust clients to do the right thing.
4750        Spannable sp = (Spannable)getText();
4751        final int N = sp.length();
4752        int start = text.selectionStart;
4753        if (start < 0) start = 0;
4754        else if (start > N) start = N;
4755        int end = text.selectionEnd;
4756        if (end < 0) end = 0;
4757        else if (end > N) end = N;
4758        Selection.setSelection(sp, start, end);
4759
4760        // Finally, update the selection mode.
4761        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
4762            MetaKeyKeyListener.startSelecting(this, sp);
4763        } else {
4764            MetaKeyKeyListener.stopSelecting(this, sp);
4765        }
4766    }
4767
4768    /**
4769     * @hide
4770     */
4771    public void setExtracting(ExtractedTextRequest req) {
4772        if (mInputMethodState != null) {
4773            mInputMethodState.mExtracting = req;
4774        }
4775        hideControllers();
4776    }
4777
4778    /**
4779     * Called by the framework in response to a text completion from
4780     * the current input method, provided by it calling
4781     * {@link InputConnection#commitCompletion
4782     * InputConnection.commitCompletion()}.  The default implementation does
4783     * nothing; text views that are supporting auto-completion should override
4784     * this to do their desired behavior.
4785     *
4786     * @param text The auto complete text the user has selected.
4787     */
4788    public void onCommitCompletion(CompletionInfo text) {
4789    }
4790
4791    public void beginBatchEdit() {
4792        final InputMethodState ims = mInputMethodState;
4793        if (ims != null) {
4794            int nesting = ++ims.mBatchEditNesting;
4795            if (nesting == 1) {
4796                ims.mCursorChanged = false;
4797                ims.mChangedDelta = 0;
4798                if (ims.mContentChanged) {
4799                    // We already have a pending change from somewhere else,
4800                    // so turn this into a full update.
4801                    ims.mChangedStart = 0;
4802                    ims.mChangedEnd = mText.length();
4803                } else {
4804                    ims.mChangedStart = EXTRACT_UNKNOWN;
4805                    ims.mChangedEnd = EXTRACT_UNKNOWN;
4806                    ims.mContentChanged = false;
4807                }
4808                onBeginBatchEdit();
4809            }
4810        }
4811    }
4812
4813    public void endBatchEdit() {
4814        final InputMethodState ims = mInputMethodState;
4815        if (ims != null) {
4816            int nesting = --ims.mBatchEditNesting;
4817            if (nesting == 0) {
4818                finishBatchEdit(ims);
4819            }
4820        }
4821    }
4822
4823    void ensureEndedBatchEdit() {
4824        final InputMethodState ims = mInputMethodState;
4825        if (ims != null && ims.mBatchEditNesting != 0) {
4826            ims.mBatchEditNesting = 0;
4827            finishBatchEdit(ims);
4828        }
4829    }
4830
4831    void finishBatchEdit(final InputMethodState ims) {
4832        onEndBatchEdit();
4833
4834        if (ims.mContentChanged || ims.mSelectionModeChanged) {
4835            updateAfterEdit();
4836            reportExtractedText();
4837        } else if (ims.mCursorChanged) {
4838            // Cheezy way to get us to report the current cursor location.
4839            invalidateCursor();
4840        }
4841    }
4842
4843    void updateAfterEdit() {
4844        invalidate();
4845        int curs = getSelectionStart();
4846
4847        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4848                             Gravity.BOTTOM) {
4849            registerForPreDraw();
4850        }
4851
4852        if (curs >= 0) {
4853            mHighlightPathBogus = true;
4854
4855            if (isFocused()) {
4856                mShowCursor = SystemClock.uptimeMillis();
4857                makeBlink();
4858            }
4859        }
4860
4861        checkForResize();
4862    }
4863
4864    /**
4865     * Called by the framework in response to a request to begin a batch
4866     * of edit operations through a call to link {@link #beginBatchEdit()}.
4867     */
4868    public void onBeginBatchEdit() {
4869    }
4870
4871    /**
4872     * Called by the framework in response to a request to end a batch
4873     * of edit operations through a call to link {@link #endBatchEdit}.
4874     */
4875    public void onEndBatchEdit() {
4876    }
4877
4878    /**
4879     * Called by the framework in response to a private command from the
4880     * current method, provided by it calling
4881     * {@link InputConnection#performPrivateCommand
4882     * InputConnection.performPrivateCommand()}.
4883     *
4884     * @param action The action name of the command.
4885     * @param data Any additional data for the command.  This may be null.
4886     * @return Return true if you handled the command, else false.
4887     */
4888    public boolean onPrivateIMECommand(String action, Bundle data) {
4889        return false;
4890    }
4891
4892    private void nullLayouts() {
4893        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
4894            mSavedLayout = (BoringLayout) mLayout;
4895        }
4896        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
4897            mSavedHintLayout = (BoringLayout) mHintLayout;
4898        }
4899
4900        mLayout = mHintLayout = null;
4901    }
4902
4903    /**
4904     * Make a new Layout based on the already-measured size of the view,
4905     * on the assumption that it was measured correctly at some point.
4906     */
4907    private void assumeLayout() {
4908        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4909
4910        if (width < 1) {
4911            width = 0;
4912        }
4913
4914        int physicalWidth = width;
4915
4916        if (mHorizontallyScrolling) {
4917            width = VERY_WIDE;
4918        }
4919
4920        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
4921                      physicalWidth, false);
4922    }
4923
4924    /**
4925     * The width passed in is now the desired layout width,
4926     * not the full view width with padding.
4927     * {@hide}
4928     */
4929    protected void makeNewLayout(int w, int hintWidth,
4930                                 BoringLayout.Metrics boring,
4931                                 BoringLayout.Metrics hintBoring,
4932                                 int ellipsisWidth, boolean bringIntoView) {
4933        stopMarquee();
4934
4935        mHighlightPathBogus = true;
4936
4937        if (w < 0) {
4938            w = 0;
4939        }
4940        if (hintWidth < 0) {
4941            hintWidth = 0;
4942        }
4943
4944        Layout.Alignment alignment;
4945        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
4946            case Gravity.CENTER_HORIZONTAL:
4947                alignment = Layout.Alignment.ALIGN_CENTER;
4948                break;
4949
4950            case Gravity.RIGHT:
4951                alignment = Layout.Alignment.ALIGN_OPPOSITE;
4952                break;
4953
4954            default:
4955                alignment = Layout.Alignment.ALIGN_NORMAL;
4956        }
4957
4958        boolean shouldEllipsize = mEllipsize != null && mInput == null;
4959
4960        if (mText instanceof Spannable) {
4961            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
4962                    alignment, mSpacingMult,
4963                    mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
4964                    ellipsisWidth);
4965        } else {
4966            if (boring == UNKNOWN_BORING) {
4967                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4968                                               mBoring);
4969                if (boring != null) {
4970                    mBoring = boring;
4971                }
4972            }
4973
4974            if (boring != null) {
4975                if (boring.width <= w &&
4976                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
4977                    if (mSavedLayout != null) {
4978                        mLayout = mSavedLayout.
4979                                replaceOrMake(mTransformed, mTextPaint,
4980                                w, alignment, mSpacingMult, mSpacingAdd,
4981                                boring, mIncludePad);
4982                    } else {
4983                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4984                                w, alignment, mSpacingMult, mSpacingAdd,
4985                                boring, mIncludePad);
4986                    }
4987
4988                    mSavedLayout = (BoringLayout) mLayout;
4989                } else if (shouldEllipsize && boring.width <= w) {
4990                    if (mSavedLayout != null) {
4991                        mLayout = mSavedLayout.
4992                                replaceOrMake(mTransformed, mTextPaint,
4993                                w, alignment, mSpacingMult, mSpacingAdd,
4994                                boring, mIncludePad, mEllipsize,
4995                                ellipsisWidth);
4996                    } else {
4997                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4998                                w, alignment, mSpacingMult, mSpacingAdd,
4999                                boring, mIncludePad, mEllipsize,
5000                                ellipsisWidth);
5001                    }
5002                } else if (shouldEllipsize) {
5003                    mLayout = new StaticLayout(mTransformed,
5004                                0, mTransformed.length(),
5005                                mTextPaint, w, alignment, mSpacingMult,
5006                                mSpacingAdd, mIncludePad, mEllipsize,
5007                                ellipsisWidth);
5008                } else {
5009                    mLayout = new StaticLayout(mTransformed, mTextPaint,
5010                            w, alignment, mSpacingMult, mSpacingAdd,
5011                            mIncludePad);
5012                }
5013            } else if (shouldEllipsize) {
5014                mLayout = new StaticLayout(mTransformed,
5015                            0, mTransformed.length(),
5016                            mTextPaint, w, alignment, mSpacingMult,
5017                            mSpacingAdd, mIncludePad, mEllipsize,
5018                            ellipsisWidth);
5019            } else {
5020                mLayout = new StaticLayout(mTransformed, mTextPaint,
5021                        w, alignment, mSpacingMult, mSpacingAdd,
5022                        mIncludePad);
5023            }
5024        }
5025
5026        shouldEllipsize = mEllipsize != null;
5027        mHintLayout = null;
5028
5029        if (mHint != null) {
5030            if (shouldEllipsize) hintWidth = w;
5031
5032            if (hintBoring == UNKNOWN_BORING) {
5033                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
5034                                                   mHintBoring);
5035                if (hintBoring != null) {
5036                    mHintBoring = hintBoring;
5037                }
5038            }
5039
5040            if (hintBoring != null) {
5041                if (hintBoring.width <= hintWidth &&
5042                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
5043                    if (mSavedHintLayout != null) {
5044                        mHintLayout = mSavedHintLayout.
5045                                replaceOrMake(mHint, mTextPaint,
5046                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5047                                hintBoring, mIncludePad);
5048                    } else {
5049                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
5050                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5051                                hintBoring, mIncludePad);
5052                    }
5053
5054                    mSavedHintLayout = (BoringLayout) mHintLayout;
5055                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5056                    if (mSavedHintLayout != null) {
5057                        mHintLayout = mSavedHintLayout.
5058                                replaceOrMake(mHint, mTextPaint,
5059                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5060                                hintBoring, mIncludePad, mEllipsize,
5061                                ellipsisWidth);
5062                    } else {
5063                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
5064                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5065                                hintBoring, mIncludePad, mEllipsize,
5066                                ellipsisWidth);
5067                    }
5068                } else if (shouldEllipsize) {
5069                    mHintLayout = new StaticLayout(mHint,
5070                                0, mHint.length(),
5071                                mTextPaint, hintWidth, alignment, mSpacingMult,
5072                                mSpacingAdd, mIncludePad, mEllipsize,
5073                                ellipsisWidth);
5074                } else {
5075                    mHintLayout = new StaticLayout(mHint, mTextPaint,
5076                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
5077                            mIncludePad);
5078                }
5079            } else if (shouldEllipsize) {
5080                mHintLayout = new StaticLayout(mHint,
5081                            0, mHint.length(),
5082                            mTextPaint, hintWidth, alignment, mSpacingMult,
5083                            mSpacingAdd, mIncludePad, mEllipsize,
5084                            ellipsisWidth);
5085            } else {
5086                mHintLayout = new StaticLayout(mHint, mTextPaint,
5087                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
5088                        mIncludePad);
5089            }
5090        }
5091
5092        if (bringIntoView) {
5093            registerForPreDraw();
5094        }
5095
5096        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5097            if (!compressText(ellipsisWidth)) {
5098                final int height = mLayoutParams.height;
5099                // If the size of the view does not depend on the size of the text, try to
5100                // start the marquee immediately
5101                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
5102                    startMarquee();
5103                } else {
5104                    // Defer the start of the marquee until we know our width (see setFrame())
5105                    mRestartMarquee = true;
5106                }
5107            }
5108        }
5109
5110        // CursorControllers need a non-null mLayout
5111        prepareCursorControllers();
5112    }
5113
5114    private boolean compressText(float width) {
5115        // Only compress the text if it hasn't been compressed by the previous pass
5116        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
5117                mTextPaint.getTextScaleX() == 1.0f) {
5118            final float textWidth = mLayout.getLineWidth(0);
5119            final float overflow = (textWidth + 1.0f - width) / width;
5120            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
5121                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
5122                post(new Runnable() {
5123                    public void run() {
5124                        requestLayout();
5125                    }
5126                });
5127                return true;
5128            }
5129        }
5130
5131        return false;
5132    }
5133
5134    private static int desired(Layout layout) {
5135        int n = layout.getLineCount();
5136        CharSequence text = layout.getText();
5137        float max = 0;
5138
5139        // if any line was wrapped, we can't use it.
5140        // but it's ok for the last line not to have a newline
5141
5142        for (int i = 0; i < n - 1; i++) {
5143            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
5144                return -1;
5145        }
5146
5147        for (int i = 0; i < n; i++) {
5148            max = Math.max(max, layout.getLineWidth(i));
5149        }
5150
5151        return (int) FloatMath.ceil(max);
5152    }
5153
5154    /**
5155     * Set whether the TextView includes extra top and bottom padding to make
5156     * room for accents that go above the normal ascent and descent.
5157     * The default is true.
5158     *
5159     * @attr ref android.R.styleable#TextView_includeFontPadding
5160     */
5161    public void setIncludeFontPadding(boolean includepad) {
5162        mIncludePad = includepad;
5163
5164        if (mLayout != null) {
5165            nullLayouts();
5166            requestLayout();
5167            invalidate();
5168        }
5169    }
5170
5171    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
5172
5173    @Override
5174    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5175        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
5176        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
5177        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
5178        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
5179
5180        int width;
5181        int height;
5182
5183        BoringLayout.Metrics boring = UNKNOWN_BORING;
5184        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
5185
5186        int des = -1;
5187        boolean fromexisting = false;
5188
5189        if (widthMode == MeasureSpec.EXACTLY) {
5190            // Parent has told us how big to be. So be it.
5191            width = widthSize;
5192        } else {
5193            if (mLayout != null && mEllipsize == null) {
5194                des = desired(mLayout);
5195            }
5196
5197            if (des < 0) {
5198                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
5199                if (boring != null) {
5200                    mBoring = boring;
5201                }
5202            } else {
5203                fromexisting = true;
5204            }
5205
5206            if (boring == null || boring == UNKNOWN_BORING) {
5207                if (des < 0) {
5208                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
5209                }
5210
5211                width = des;
5212            } else {
5213                width = boring.width;
5214            }
5215
5216            final Drawables dr = mDrawables;
5217            if (dr != null) {
5218                width = Math.max(width, dr.mDrawableWidthTop);
5219                width = Math.max(width, dr.mDrawableWidthBottom);
5220            }
5221
5222            if (mHint != null) {
5223                int hintDes = -1;
5224                int hintWidth;
5225
5226                if (mHintLayout != null && mEllipsize == null) {
5227                    hintDes = desired(mHintLayout);
5228                }
5229
5230                if (hintDes < 0) {
5231                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
5232                    if (hintBoring != null) {
5233                        mHintBoring = hintBoring;
5234                    }
5235                }
5236
5237                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
5238                    if (hintDes < 0) {
5239                        hintDes = (int) FloatMath.ceil(
5240                                Layout.getDesiredWidth(mHint, mTextPaint));
5241                    }
5242
5243                    hintWidth = hintDes;
5244                } else {
5245                    hintWidth = hintBoring.width;
5246                }
5247
5248                if (hintWidth > width) {
5249                    width = hintWidth;
5250                }
5251            }
5252
5253            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
5254
5255            if (mMaxWidthMode == EMS) {
5256                width = Math.min(width, mMaxWidth * getLineHeight());
5257            } else {
5258                width = Math.min(width, mMaxWidth);
5259            }
5260
5261            if (mMinWidthMode == EMS) {
5262                width = Math.max(width, mMinWidth * getLineHeight());
5263            } else {
5264                width = Math.max(width, mMinWidth);
5265            }
5266
5267            // Check against our minimum width
5268            width = Math.max(width, getSuggestedMinimumWidth());
5269
5270            if (widthMode == MeasureSpec.AT_MOST) {
5271                width = Math.min(widthSize, width);
5272            }
5273        }
5274
5275        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5276        int unpaddedWidth = want;
5277        int hintWant = want;
5278
5279        if (mHorizontallyScrolling)
5280            want = VERY_WIDE;
5281
5282        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5283
5284        if (mLayout == null) {
5285            makeNewLayout(want, hintWant, boring, hintBoring,
5286                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5287        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5288                   (mLayout.getEllipsizedWidth() !=
5289                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5290            if (mHint == null && mEllipsize == null &&
5291                    want > mLayout.getWidth() &&
5292                    (mLayout instanceof BoringLayout ||
5293                            (fromexisting && des >= 0 && des <= want))) {
5294                mLayout.increaseWidthTo(want);
5295            } else {
5296                makeNewLayout(want, hintWant, boring, hintBoring,
5297                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5298            }
5299        } else {
5300            // Width has not changed.
5301        }
5302
5303        if (heightMode == MeasureSpec.EXACTLY) {
5304            // Parent has told us how big to be. So be it.
5305            height = heightSize;
5306            mDesiredHeightAtMeasure = -1;
5307        } else {
5308            int desired = getDesiredHeight();
5309
5310            height = desired;
5311            mDesiredHeightAtMeasure = desired;
5312
5313            if (heightMode == MeasureSpec.AT_MOST) {
5314                height = Math.min(desired, heightSize);
5315            }
5316        }
5317
5318        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
5319        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
5320            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
5321        }
5322
5323        /*
5324         * We didn't let makeNewLayout() register to bring the cursor into view,
5325         * so do it here if there is any possibility that it is needed.
5326         */
5327        if (mMovement != null ||
5328            mLayout.getWidth() > unpaddedWidth ||
5329            mLayout.getHeight() > unpaddedHeight) {
5330            registerForPreDraw();
5331        } else {
5332            scrollTo(0, 0);
5333        }
5334
5335        setMeasuredDimension(width, height);
5336    }
5337
5338    private int getDesiredHeight() {
5339        return Math.max(
5340                getDesiredHeight(mLayout, true),
5341                getDesiredHeight(mHintLayout, mEllipsize != null));
5342    }
5343
5344    private int getDesiredHeight(Layout layout, boolean cap) {
5345        if (layout == null) {
5346            return 0;
5347        }
5348
5349        int linecount = layout.getLineCount();
5350        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5351        int desired = layout.getLineTop(linecount);
5352
5353        final Drawables dr = mDrawables;
5354        if (dr != null) {
5355            desired = Math.max(desired, dr.mDrawableHeightLeft);
5356            desired = Math.max(desired, dr.mDrawableHeightRight);
5357        }
5358
5359        desired += pad;
5360
5361        if (mMaxMode == LINES) {
5362            /*
5363             * Don't cap the hint to a certain number of lines.
5364             * (Do cap it, though, if we have a maximum pixel height.)
5365             */
5366            if (cap) {
5367                if (linecount > mMaximum) {
5368                    desired = layout.getLineTop(mMaximum) +
5369                              layout.getBottomPadding();
5370
5371                    if (dr != null) {
5372                        desired = Math.max(desired, dr.mDrawableHeightLeft);
5373                        desired = Math.max(desired, dr.mDrawableHeightRight);
5374                    }
5375
5376                    desired += pad;
5377                    linecount = mMaximum;
5378                }
5379            }
5380        } else {
5381            desired = Math.min(desired, mMaximum);
5382        }
5383
5384        if (mMinMode == LINES) {
5385            if (linecount < mMinimum) {
5386                desired += getLineHeight() * (mMinimum - linecount);
5387            }
5388        } else {
5389            desired = Math.max(desired, mMinimum);
5390        }
5391
5392        // Check against our minimum height
5393        desired = Math.max(desired, getSuggestedMinimumHeight());
5394
5395        return desired;
5396    }
5397
5398    /**
5399     * Check whether a change to the existing text layout requires a
5400     * new view layout.
5401     */
5402    private void checkForResize() {
5403        boolean sizeChanged = false;
5404
5405        if (mLayout != null) {
5406            // Check if our width changed
5407            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
5408                sizeChanged = true;
5409                invalidate();
5410            }
5411
5412            // Check if our height changed
5413            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
5414                int desiredHeight = getDesiredHeight();
5415
5416                if (desiredHeight != this.getHeight()) {
5417                    sizeChanged = true;
5418                }
5419            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
5420                if (mDesiredHeightAtMeasure >= 0) {
5421                    int desiredHeight = getDesiredHeight();
5422
5423                    if (desiredHeight != mDesiredHeightAtMeasure) {
5424                        sizeChanged = true;
5425                    }
5426                }
5427            }
5428        }
5429
5430        if (sizeChanged) {
5431            requestLayout();
5432            // caller will have already invalidated
5433        }
5434    }
5435
5436    /**
5437     * Check whether entirely new text requires a new view layout
5438     * or merely a new text layout.
5439     */
5440    private void checkForRelayout() {
5441        // If we have a fixed width, we can just swap in a new text layout
5442        // if the text height stays the same or if the view height is fixed.
5443
5444        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
5445                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
5446                (mHint == null || mHintLayout != null) &&
5447                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
5448            // Static width, so try making a new text layout.
5449
5450            int oldht = mLayout.getHeight();
5451            int want = mLayout.getWidth();
5452            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5453
5454            /*
5455             * No need to bring the text into view, since the size is not
5456             * changing (unless we do the requestLayout(), in which case it
5457             * will happen at measure).
5458             */
5459            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5460                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
5461                          false);
5462
5463            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
5464                // In a fixed-height view, so use our new text layout.
5465                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
5466                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
5467                    invalidate();
5468                    return;
5469                }
5470
5471                // Dynamic height, but height has stayed the same,
5472                // so use our new text layout.
5473                if (mLayout.getHeight() == oldht &&
5474                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
5475                    invalidate();
5476                    return;
5477                }
5478            }
5479
5480            // We lose: the height has changed and we have a dynamic height.
5481            // Request a new view layout using our new text layout.
5482            requestLayout();
5483            invalidate();
5484        } else {
5485            // Dynamic width, so we have no choice but to request a new
5486            // view layout with a new text layout.
5487
5488            nullLayouts();
5489            requestLayout();
5490            invalidate();
5491        }
5492    }
5493
5494    /**
5495     * Returns true if anything changed.
5496     */
5497    private boolean bringTextIntoView() {
5498        int line = 0;
5499        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5500            line = mLayout.getLineCount() - 1;
5501        }
5502
5503        Layout.Alignment a = mLayout.getParagraphAlignment(line);
5504        int dir = mLayout.getParagraphDirection(line);
5505        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5506        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5507        int ht = mLayout.getHeight();
5508
5509        int scrollx, scrolly;
5510
5511        if (a == Layout.Alignment.ALIGN_CENTER) {
5512            /*
5513             * Keep centered if possible, or, if it is too wide to fit,
5514             * keep leading edge in view.
5515             */
5516
5517            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5518            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5519
5520            if (right - left < hspace) {
5521                scrollx = (right + left) / 2 - hspace / 2;
5522            } else {
5523                if (dir < 0) {
5524                    scrollx = right - hspace;
5525                } else {
5526                    scrollx = left;
5527                }
5528            }
5529        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
5530            /*
5531             * Keep leading edge in view.
5532             */
5533
5534            if (dir < 0) {
5535                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5536                scrollx = right - hspace;
5537            } else {
5538                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5539            }
5540        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
5541            /*
5542             * Keep trailing edge in view.
5543             */
5544
5545            if (dir < 0) {
5546                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5547            } else {
5548                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5549                scrollx = right - hspace;
5550            }
5551        }
5552
5553        if (ht < vspace) {
5554            scrolly = 0;
5555        } else {
5556            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5557                scrolly = ht - vspace;
5558            } else {
5559                scrolly = 0;
5560            }
5561        }
5562
5563        if (scrollx != mScrollX || scrolly != mScrollY) {
5564            scrollTo(scrollx, scrolly);
5565            return true;
5566        } else {
5567            return false;
5568        }
5569    }
5570
5571    /**
5572     * Move the point, specified by the offset, into the view if it is needed.
5573     * This has to be called after layout. Returns true if anything changed.
5574     */
5575    public boolean bringPointIntoView(int offset) {
5576        boolean changed = false;
5577
5578        int line = mLayout.getLineForOffset(offset);
5579
5580        // FIXME: Is it okay to truncate this, or should we round?
5581        final int x = (int)mLayout.getPrimaryHorizontal(offset);
5582        final int top = mLayout.getLineTop(line);
5583        final int bottom = mLayout.getLineTop(line + 1);
5584
5585        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5586        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5587        int ht = mLayout.getHeight();
5588
5589        int grav;
5590
5591        switch (mLayout.getParagraphAlignment(line)) {
5592            case ALIGN_NORMAL:
5593                grav = 1;
5594                break;
5595
5596            case ALIGN_OPPOSITE:
5597                grav = -1;
5598                break;
5599
5600            default:
5601                grav = 0;
5602        }
5603
5604        grav *= mLayout.getParagraphDirection(line);
5605
5606        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5607        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5608
5609        int hslack = (bottom - top) / 2;
5610        int vslack = hslack;
5611
5612        if (vslack > vspace / 4)
5613            vslack = vspace / 4;
5614        if (hslack > hspace / 4)
5615            hslack = hspace / 4;
5616
5617        int hs = mScrollX;
5618        int vs = mScrollY;
5619
5620        if (top - vs < vslack)
5621            vs = top - vslack;
5622        if (bottom - vs > vspace - vslack)
5623            vs = bottom - (vspace - vslack);
5624        if (ht - vs < vspace)
5625            vs = ht - vspace;
5626        if (0 - vs > 0)
5627            vs = 0;
5628
5629        if (grav != 0) {
5630            if (x - hs < hslack) {
5631                hs = x - hslack;
5632            }
5633            if (x - hs > hspace - hslack) {
5634                hs = x - (hspace - hslack);
5635            }
5636        }
5637
5638        if (grav < 0) {
5639            if (left - hs > 0)
5640                hs = left;
5641            if (right - hs < hspace)
5642                hs = right - hspace;
5643        } else if (grav > 0) {
5644            if (right - hs < hspace)
5645                hs = right - hspace;
5646            if (left - hs > 0)
5647                hs = left;
5648        } else /* grav == 0 */ {
5649            if (right - left <= hspace) {
5650                /*
5651                 * If the entire text fits, center it exactly.
5652                 */
5653                hs = left - (hspace - (right - left)) / 2;
5654            } else if (x > right - hslack) {
5655                /*
5656                 * If we are near the right edge, keep the right edge
5657                 * at the edge of the view.
5658                 */
5659                hs = right - hspace;
5660            } else if (x < left + hslack) {
5661                /*
5662                 * If we are near the left edge, keep the left edge
5663                 * at the edge of the view.
5664                 */
5665                hs = left;
5666            } else if (left > hs) {
5667                /*
5668                 * Is there whitespace visible at the left?  Fix it if so.
5669                 */
5670                hs = left;
5671            } else if (right < hs + hspace) {
5672                /*
5673                 * Is there whitespace visible at the right?  Fix it if so.
5674                 */
5675                hs = right - hspace;
5676            } else {
5677                /*
5678                 * Otherwise, float as needed.
5679                 */
5680                if (x - hs < hslack) {
5681                    hs = x - hslack;
5682                }
5683                if (x - hs > hspace - hslack) {
5684                    hs = x - (hspace - hslack);
5685                }
5686            }
5687        }
5688
5689        if (hs != mScrollX || vs != mScrollY) {
5690            if (mScroller == null) {
5691                scrollTo(hs, vs);
5692            } else {
5693                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
5694                int dx = hs - mScrollX;
5695                int dy = vs - mScrollY;
5696
5697                if (duration > ANIMATED_SCROLL_GAP) {
5698                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
5699                    awakenScrollBars(mScroller.getDuration());
5700                    invalidate();
5701                } else {
5702                    if (!mScroller.isFinished()) {
5703                        mScroller.abortAnimation();
5704                    }
5705
5706                    scrollBy(dx, dy);
5707                }
5708
5709                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
5710            }
5711
5712            changed = true;
5713        }
5714
5715        if (isFocused()) {
5716            // This offsets because getInterestingRect() is in terms of
5717            // viewport coordinates, but requestRectangleOnScreen()
5718            // is in terms of content coordinates.
5719
5720            Rect r = new Rect(x, top, x + 1, bottom);
5721            getInterestingRect(r, line);
5722            r.offset(mScrollX, mScrollY);
5723
5724            if (requestRectangleOnScreen(r)) {
5725                changed = true;
5726            }
5727        }
5728
5729        return changed;
5730    }
5731
5732    /**
5733     * Move the cursor, if needed, so that it is at an offset that is visible
5734     * to the user.  This will not move the cursor if it represents more than
5735     * one character (a selection range).  This will only work if the
5736     * TextView contains spannable text; otherwise it will do nothing.
5737     *
5738     * @return True if the cursor was actually moved, false otherwise.
5739     */
5740    public boolean moveCursorToVisibleOffset() {
5741        if (!(mText instanceof Spannable)) {
5742            return false;
5743        }
5744        int start = getSelectionStart();
5745        int end = getSelectionEnd();
5746        if (start != end) {
5747            return false;
5748        }
5749
5750        // First: make sure the line is visible on screen:
5751
5752        int line = mLayout.getLineForOffset(start);
5753
5754        final int top = mLayout.getLineTop(line);
5755        final int bottom = mLayout.getLineTop(line + 1);
5756        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5757        int vslack = (bottom - top) / 2;
5758        if (vslack > vspace / 4)
5759            vslack = vspace / 4;
5760        final int vs = mScrollY;
5761
5762        if (top < (vs+vslack)) {
5763            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
5764        } else if (bottom > (vspace+vs-vslack)) {
5765            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
5766        }
5767
5768        // Next: make sure the character is visible on screen:
5769
5770        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5771        final int hs = mScrollX;
5772        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
5773        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
5774
5775        int newStart = start;
5776        if (newStart < leftChar) {
5777            newStart = leftChar;
5778        } else if (newStart > rightChar) {
5779            newStart = rightChar;
5780        }
5781
5782        if (newStart != start) {
5783            Selection.setSelection((Spannable)mText, newStart);
5784            return true;
5785        }
5786
5787        return false;
5788    }
5789
5790    @Override
5791    public void computeScroll() {
5792        if (mScroller != null) {
5793            if (mScroller.computeScrollOffset()) {
5794                mScrollX = mScroller.getCurrX();
5795                mScrollY = mScroller.getCurrY();
5796                postInvalidate();  // So we draw again
5797            }
5798        }
5799    }
5800
5801    private void getInterestingRect(Rect r, int line) {
5802        convertFromViewportToContentCoordinates(r);
5803
5804        // Rectangle can can be expanded on first and last line to take
5805        // padding into account.
5806        // TODO Take left/right padding into account too?
5807        if (line == 0) r.top -= getExtendedPaddingTop();
5808        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
5809    }
5810
5811    private void convertFromViewportToContentCoordinates(Rect r) {
5812        final int horizontalOffset = viewportToContentHorizontalOffset();
5813        r.left += horizontalOffset;
5814        r.right += horizontalOffset;
5815
5816        final int verticalOffset = viewportToContentVerticalOffset();
5817        r.top += verticalOffset;
5818        r.bottom += verticalOffset;
5819    }
5820
5821    private int viewportToContentHorizontalOffset() {
5822        return getCompoundPaddingLeft() - mScrollX;
5823    }
5824
5825    private int viewportToContentVerticalOffset() {
5826        int offset = getExtendedPaddingTop() - mScrollY;
5827        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5828            offset += getVerticalOffset(false);
5829        }
5830        return offset;
5831    }
5832
5833    @Override
5834    public void debug(int depth) {
5835        super.debug(depth);
5836
5837        String output = debugIndent(depth);
5838        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
5839                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
5840                + "} ";
5841
5842        if (mText != null) {
5843
5844            output += "mText=\"" + mText + "\" ";
5845            if (mLayout != null) {
5846                output += "mLayout width=" + mLayout.getWidth()
5847                        + " height=" + mLayout.getHeight();
5848            }
5849        } else {
5850            output += "mText=NULL";
5851        }
5852        Log.d(VIEW_LOG_TAG, output);
5853    }
5854
5855    /**
5856     * Convenience for {@link Selection#getSelectionStart}.
5857     */
5858    @ViewDebug.ExportedProperty(category = "text")
5859    public int getSelectionStart() {
5860        return Selection.getSelectionStart(getText());
5861    }
5862
5863    /**
5864     * Convenience for {@link Selection#getSelectionEnd}.
5865     */
5866    @ViewDebug.ExportedProperty(category = "text")
5867    public int getSelectionEnd() {
5868        return Selection.getSelectionEnd(getText());
5869    }
5870
5871    /**
5872     * Return true iff there is a selection inside this text view.
5873     */
5874    public boolean hasSelection() {
5875        final int selectionStart = getSelectionStart();
5876        final int selectionEnd = getSelectionEnd();
5877
5878        return selectionStart >= 0 && selectionStart != selectionEnd;
5879    }
5880
5881    /**
5882     * Sets the properties of this field (lines, horizontally scrolling,
5883     * transformation method) to be for a single-line input.
5884     *
5885     * @attr ref android.R.styleable#TextView_singleLine
5886     */
5887    public void setSingleLine() {
5888        setSingleLine(true);
5889    }
5890
5891    /**
5892     * If true, sets the properties of this field (lines, horizontally
5893     * scrolling, transformation method) to be for a single-line input;
5894     * if false, restores these to the default conditions.
5895     * Note that calling this with false restores default conditions,
5896     * not necessarily those that were in effect prior to calling
5897     * it with true.
5898     *
5899     * @attr ref android.R.styleable#TextView_singleLine
5900     */
5901    @android.view.RemotableViewMethod
5902    public void setSingleLine(boolean singleLine) {
5903        if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
5904                == EditorInfo.TYPE_CLASS_TEXT) {
5905            if (singleLine) {
5906                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5907            } else {
5908                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5909            }
5910        }
5911        applySingleLine(singleLine, true);
5912    }
5913
5914    private void applySingleLine(boolean singleLine, boolean applyTransformation) {
5915        mSingleLine = singleLine;
5916        if (singleLine) {
5917            setLines(1);
5918            setHorizontallyScrolling(true);
5919            if (applyTransformation) {
5920                setTransformationMethod(SingleLineTransformationMethod.
5921                                        getInstance());
5922            }
5923        } else {
5924            setMaxLines(Integer.MAX_VALUE);
5925            setHorizontallyScrolling(false);
5926            if (applyTransformation) {
5927                setTransformationMethod(null);
5928            }
5929        }
5930    }
5931
5932    /**
5933     * Causes words in the text that are longer than the view is wide
5934     * to be ellipsized instead of broken in the middle.  You may also
5935     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
5936     * to constrain the text to a single line.  Use <code>null</code>
5937     * to turn off ellipsizing.
5938     *
5939     * @attr ref android.R.styleable#TextView_ellipsize
5940     */
5941    public void setEllipsize(TextUtils.TruncateAt where) {
5942        mEllipsize = where;
5943
5944        if (mLayout != null) {
5945            nullLayouts();
5946            requestLayout();
5947            invalidate();
5948        }
5949    }
5950
5951    /**
5952     * Sets how many times to repeat the marquee animation. Only applied if the
5953     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
5954     *
5955     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
5956     */
5957    public void setMarqueeRepeatLimit(int marqueeLimit) {
5958        mMarqueeRepeatLimit = marqueeLimit;
5959    }
5960
5961    /**
5962     * Returns where, if anywhere, words that are longer than the view
5963     * is wide should be ellipsized.
5964     */
5965    @ViewDebug.ExportedProperty
5966    public TextUtils.TruncateAt getEllipsize() {
5967        return mEllipsize;
5968    }
5969
5970    /**
5971     * Set the TextView so that when it takes focus, all the text is
5972     * selected.
5973     *
5974     * @attr ref android.R.styleable#TextView_selectAllOnFocus
5975     */
5976    @android.view.RemotableViewMethod
5977    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
5978        mSelectAllOnFocus = selectAllOnFocus;
5979
5980        if (selectAllOnFocus && !(mText instanceof Spannable)) {
5981            setText(mText, BufferType.SPANNABLE);
5982        }
5983    }
5984
5985    /**
5986     * Set whether the cursor is visible.  The default is true.
5987     *
5988     * @attr ref android.R.styleable#TextView_cursorVisible
5989     */
5990    @android.view.RemotableViewMethod
5991    public void setCursorVisible(boolean visible) {
5992        mCursorVisible = visible;
5993        invalidate();
5994
5995        if (visible) {
5996            makeBlink();
5997        } else if (mBlink != null) {
5998            mBlink.removeCallbacks(mBlink);
5999        }
6000
6001        // InsertionPointCursorController depends on mCursorVisible
6002        prepareCursorControllers();
6003    }
6004
6005    private boolean canMarquee() {
6006        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
6007        return width > 0 && mLayout.getLineWidth(0) > width;
6008    }
6009
6010    private void startMarquee() {
6011        // Do not ellipsize EditText
6012        if (mInput != null) return;
6013
6014        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
6015            return;
6016        }
6017
6018        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
6019                getLineCount() == 1 && canMarquee()) {
6020
6021            if (mMarquee == null) mMarquee = new Marquee(this);
6022            mMarquee.start(mMarqueeRepeatLimit);
6023        }
6024    }
6025
6026    private void stopMarquee() {
6027        if (mMarquee != null && !mMarquee.isStopped()) {
6028            mMarquee.stop();
6029        }
6030    }
6031
6032    private void startStopMarquee(boolean start) {
6033        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6034            if (start) {
6035                startMarquee();
6036            } else {
6037                stopMarquee();
6038            }
6039        }
6040    }
6041
6042    private static final class Marquee extends Handler {
6043        // TODO: Add an option to configure this
6044        private static final float MARQUEE_DELTA_MAX = 0.07f;
6045        private static final int MARQUEE_DELAY = 1200;
6046        private static final int MARQUEE_RESTART_DELAY = 1200;
6047        private static final int MARQUEE_RESOLUTION = 1000 / 30;
6048        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
6049
6050        private static final byte MARQUEE_STOPPED = 0x0;
6051        private static final byte MARQUEE_STARTING = 0x1;
6052        private static final byte MARQUEE_RUNNING = 0x2;
6053
6054        private static final int MESSAGE_START = 0x1;
6055        private static final int MESSAGE_TICK = 0x2;
6056        private static final int MESSAGE_RESTART = 0x3;
6057
6058        private final WeakReference<TextView> mView;
6059
6060        private byte mStatus = MARQUEE_STOPPED;
6061        private final float mScrollUnit;
6062        private float mMaxScroll;
6063        float mMaxFadeScroll;
6064        private float mGhostStart;
6065        private float mGhostOffset;
6066        private float mFadeStop;
6067        private int mRepeatLimit;
6068
6069        float mScroll;
6070
6071        Marquee(TextView v) {
6072            final float density = v.getContext().getResources().getDisplayMetrics().density;
6073            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
6074            mView = new WeakReference<TextView>(v);
6075        }
6076
6077        @Override
6078        public void handleMessage(Message msg) {
6079            switch (msg.what) {
6080                case MESSAGE_START:
6081                    mStatus = MARQUEE_RUNNING;
6082                    tick();
6083                    break;
6084                case MESSAGE_TICK:
6085                    tick();
6086                    break;
6087                case MESSAGE_RESTART:
6088                    if (mStatus == MARQUEE_RUNNING) {
6089                        if (mRepeatLimit >= 0) {
6090                            mRepeatLimit--;
6091                        }
6092                        start(mRepeatLimit);
6093                    }
6094                    break;
6095            }
6096        }
6097
6098        void tick() {
6099            if (mStatus != MARQUEE_RUNNING) {
6100                return;
6101            }
6102
6103            removeMessages(MESSAGE_TICK);
6104
6105            final TextView textView = mView.get();
6106            if (textView != null && (textView.isFocused() || textView.isSelected())) {
6107                mScroll += mScrollUnit;
6108                if (mScroll > mMaxScroll) {
6109                    mScroll = mMaxScroll;
6110                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
6111                } else {
6112                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
6113                }
6114                textView.invalidate();
6115            }
6116        }
6117
6118        void stop() {
6119            mStatus = MARQUEE_STOPPED;
6120            removeMessages(MESSAGE_START);
6121            removeMessages(MESSAGE_RESTART);
6122            removeMessages(MESSAGE_TICK);
6123            resetScroll();
6124        }
6125
6126        private void resetScroll() {
6127            mScroll = 0.0f;
6128            final TextView textView = mView.get();
6129            if (textView != null) textView.invalidate();
6130        }
6131
6132        void start(int repeatLimit) {
6133            if (repeatLimit == 0) {
6134                stop();
6135                return;
6136            }
6137            mRepeatLimit = repeatLimit;
6138            final TextView textView = mView.get();
6139            if (textView != null && textView.mLayout != null) {
6140                mStatus = MARQUEE_STARTING;
6141                mScroll = 0.0f;
6142                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
6143                        textView.getCompoundPaddingRight();
6144                final float lineWidth = textView.mLayout.getLineWidth(0);
6145                final float gap = textWidth / 3.0f;
6146                mGhostStart = lineWidth - textWidth + gap;
6147                mMaxScroll = mGhostStart + textWidth;
6148                mGhostOffset = lineWidth + gap;
6149                mFadeStop = lineWidth + textWidth / 6.0f;
6150                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
6151
6152                textView.invalidate();
6153                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
6154            }
6155        }
6156
6157        float getGhostOffset() {
6158            return mGhostOffset;
6159        }
6160
6161        boolean shouldDrawLeftFade() {
6162            return mScroll <= mFadeStop;
6163        }
6164
6165        boolean shouldDrawGhost() {
6166            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
6167        }
6168
6169        boolean isRunning() {
6170            return mStatus == MARQUEE_RUNNING;
6171        }
6172
6173        boolean isStopped() {
6174            return mStatus == MARQUEE_STOPPED;
6175        }
6176    }
6177
6178    /**
6179     * This method is called when the text is changed, in case any
6180     * subclasses would like to know.
6181     *
6182     * @param text The text the TextView is displaying.
6183     * @param start The offset of the start of the range of the text
6184     *              that was modified.
6185     * @param before The offset of the former end of the range of the
6186     *               text that was modified.  If text was simply inserted,
6187     *               this will be the same as <code>start</code>.
6188     *               If text was replaced with new text or deleted, the
6189     *               length of the old text was <code>before-start</code>.
6190     * @param after The offset of the end of the range of the text
6191     *              that was modified.  If text was simply deleted,
6192     *              this will be the same as <code>start</code>.
6193     *              If text was replaced with new text or inserted,
6194     *              the length of the new text is <code>after-start</code>.
6195     */
6196    protected void onTextChanged(CharSequence text,
6197                                 int start, int before, int after) {
6198    }
6199
6200    /**
6201     * This method is called when the selection has changed, in case any
6202     * subclasses would like to know.
6203     *
6204     * @param selStart The new selection start location.
6205     * @param selEnd The new selection end location.
6206     */
6207    protected void onSelectionChanged(int selStart, int selEnd) {
6208    }
6209
6210    /**
6211     * Adds a TextWatcher to the list of those whose methods are called
6212     * whenever this TextView's text changes.
6213     * <p>
6214     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6215     * not called after {@link #setText} calls.  Now, doing {@link #setText}
6216     * if there are any text changed listeners forces the buffer type to
6217     * Editable if it would not otherwise be and does call this method.
6218     */
6219    public void addTextChangedListener(TextWatcher watcher) {
6220        if (mListeners == null) {
6221            mListeners = new ArrayList<TextWatcher>();
6222        }
6223
6224        mListeners.add(watcher);
6225    }
6226
6227    /**
6228     * Removes the specified TextWatcher from the list of those whose
6229     * methods are called
6230     * whenever this TextView's text changes.
6231     */
6232    public void removeTextChangedListener(TextWatcher watcher) {
6233        if (mListeners != null) {
6234            int i = mListeners.indexOf(watcher);
6235
6236            if (i >= 0) {
6237                mListeners.remove(i);
6238            }
6239        }
6240    }
6241
6242    private void sendBeforeTextChanged(CharSequence text, int start, int before,
6243                                   int after) {
6244        if (mListeners != null) {
6245            final ArrayList<TextWatcher> list = mListeners;
6246            final int count = list.size();
6247            for (int i = 0; i < count; i++) {
6248                list.get(i).beforeTextChanged(text, start, before, after);
6249            }
6250        }
6251    }
6252
6253    /**
6254     * Not private so it can be called from an inner class without going
6255     * through a thunk.
6256     */
6257    void sendOnTextChanged(CharSequence text, int start, int before,
6258                                   int after) {
6259        if (mListeners != null) {
6260            final ArrayList<TextWatcher> list = mListeners;
6261            final int count = list.size();
6262            for (int i = 0; i < count; i++) {
6263                list.get(i).onTextChanged(text, start, before, after);
6264            }
6265        }
6266    }
6267
6268    /**
6269     * Not private so it can be called from an inner class without going
6270     * through a thunk.
6271     */
6272    void sendAfterTextChanged(Editable text) {
6273        if (mListeners != null) {
6274            final ArrayList<TextWatcher> list = mListeners;
6275            final int count = list.size();
6276            for (int i = 0; i < count; i++) {
6277                list.get(i).afterTextChanged(text);
6278            }
6279        }
6280    }
6281
6282    /**
6283     * Not private so it can be called from an inner class without going
6284     * through a thunk.
6285     */
6286    void handleTextChanged(CharSequence buffer, int start,
6287            int before, int after) {
6288        final InputMethodState ims = mInputMethodState;
6289        if (ims == null || ims.mBatchEditNesting == 0) {
6290            updateAfterEdit();
6291        }
6292        if (ims != null) {
6293            ims.mContentChanged = true;
6294            if (ims.mChangedStart < 0) {
6295                ims.mChangedStart = start;
6296                ims.mChangedEnd = start+before;
6297            } else {
6298                ims.mChangedStart = Math.min(ims.mChangedStart, start);
6299                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
6300            }
6301            ims.mChangedDelta += after-before;
6302        }
6303
6304        sendOnTextChanged(buffer, start, before, after);
6305        onTextChanged(buffer, start, before, after);
6306
6307        // Hide the controller if the amount of content changed
6308        if (before != after) {
6309            hideControllers();
6310        }
6311    }
6312
6313    /**
6314     * Not private so it can be called from an inner class without going
6315     * through a thunk.
6316     */
6317    void spanChange(Spanned buf, Object what, int oldStart, int newStart,
6318            int oldEnd, int newEnd) {
6319        // XXX Make the start and end move together if this ends up
6320        // spending too much time invalidating.
6321
6322        boolean selChanged = false;
6323        int newSelStart=-1, newSelEnd=-1;
6324
6325        final InputMethodState ims = mInputMethodState;
6326
6327        if (what == Selection.SELECTION_END) {
6328            mHighlightPathBogus = true;
6329            selChanged = true;
6330            newSelEnd = newStart;
6331
6332            if (!isFocused()) {
6333                mSelectionMoved = true;
6334            }
6335
6336            if (oldStart >= 0 || newStart >= 0) {
6337                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6338                registerForPreDraw();
6339
6340                if (isFocused()) {
6341                    mShowCursor = SystemClock.uptimeMillis();
6342                    makeBlink();
6343                }
6344            }
6345        }
6346
6347        if (what == Selection.SELECTION_START) {
6348            mHighlightPathBogus = true;
6349            selChanged = true;
6350            newSelStart = newStart;
6351
6352            if (!isFocused()) {
6353                mSelectionMoved = true;
6354            }
6355
6356            if (oldStart >= 0 || newStart >= 0) {
6357                int end = Selection.getSelectionEnd(buf);
6358                invalidateCursor(end, oldStart, newStart);
6359            }
6360        }
6361
6362        if (selChanged) {
6363            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6364                if (newSelStart < 0) {
6365                    newSelStart = Selection.getSelectionStart(buf);
6366                }
6367                if (newSelEnd < 0) {
6368                    newSelEnd = Selection.getSelectionEnd(buf);
6369                }
6370                onSelectionChanged(newSelStart, newSelEnd);
6371            }
6372        }
6373
6374        if (what instanceof UpdateAppearance ||
6375            what instanceof ParagraphStyle) {
6376            if (ims == null || ims.mBatchEditNesting == 0) {
6377                invalidate();
6378                mHighlightPathBogus = true;
6379                checkForResize();
6380            } else {
6381                ims.mContentChanged = true;
6382            }
6383        }
6384
6385        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6386            mHighlightPathBogus = true;
6387            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6388                ims.mSelectionModeChanged = true;
6389            }
6390
6391            if (Selection.getSelectionStart(buf) >= 0) {
6392                if (ims == null || ims.mBatchEditNesting == 0) {
6393                    invalidateCursor();
6394                } else {
6395                    ims.mCursorChanged = true;
6396                }
6397            }
6398        }
6399
6400        if (what instanceof ParcelableSpan) {
6401            // If this is a span that can be sent to a remote process,
6402            // the current extract editor would be interested in it.
6403            if (ims != null && ims.mExtracting != null) {
6404                if (ims.mBatchEditNesting != 0) {
6405                    if (oldStart >= 0) {
6406                        if (ims.mChangedStart > oldStart) {
6407                            ims.mChangedStart = oldStart;
6408                        }
6409                        if (ims.mChangedStart > oldEnd) {
6410                            ims.mChangedStart = oldEnd;
6411                        }
6412                    }
6413                    if (newStart >= 0) {
6414                        if (ims.mChangedStart > newStart) {
6415                            ims.mChangedStart = newStart;
6416                        }
6417                        if (ims.mChangedStart > newEnd) {
6418                            ims.mChangedStart = newEnd;
6419                        }
6420                    }
6421                } else {
6422                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
6423                            + oldStart + "-" + oldEnd + ","
6424                            + newStart + "-" + newEnd + what);
6425                    ims.mContentChanged = true;
6426                }
6427            }
6428        }
6429    }
6430
6431    private class ChangeWatcher
6432    implements TextWatcher, SpanWatcher {
6433
6434        private CharSequence mBeforeText;
6435
6436        public void beforeTextChanged(CharSequence buffer, int start,
6437                                      int before, int after) {
6438            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
6439                    + " before=" + before + " after=" + after + ": " + buffer);
6440
6441            if (AccessibilityManager.getInstance(mContext).isEnabled()
6442                    && !isPasswordInputType(mInputType)) {
6443                mBeforeText = buffer.toString();
6444            }
6445
6446            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
6447        }
6448
6449        public void onTextChanged(CharSequence buffer, int start,
6450                                  int before, int after) {
6451            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
6452                    + " before=" + before + " after=" + after + ": " + buffer);
6453            TextView.this.handleTextChanged(buffer, start, before, after);
6454
6455            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
6456                    (isFocused() || isSelected() &&
6457                    isShown())) {
6458                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
6459                mBeforeText = null;
6460            }
6461        }
6462
6463        public void afterTextChanged(Editable buffer) {
6464            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
6465            TextView.this.sendAfterTextChanged(buffer);
6466
6467            if (MetaKeyKeyListener.getMetaState(buffer,
6468                                 MetaKeyKeyListener.META_SELECTING) != 0) {
6469                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
6470            }
6471        }
6472
6473        public void onSpanChanged(Spannable buf,
6474                                  Object what, int s, int e, int st, int en) {
6475            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
6476                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
6477            TextView.this.spanChange(buf, what, s, st, e, en);
6478        }
6479
6480        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
6481            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
6482                    + " what=" + what + ": " + buf);
6483            TextView.this.spanChange(buf, what, -1, s, -1, e);
6484        }
6485
6486        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
6487            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
6488                    + " what=" + what + ": " + buf);
6489            TextView.this.spanChange(buf, what, s, -1, e, -1);
6490        }
6491    }
6492
6493    private void makeBlink() {
6494        if (!mCursorVisible) {
6495            if (mBlink != null) {
6496                mBlink.removeCallbacks(mBlink);
6497            }
6498
6499            return;
6500        }
6501
6502        if (mBlink == null)
6503            mBlink = new Blink(this);
6504
6505        mBlink.removeCallbacks(mBlink);
6506        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
6507    }
6508
6509    /**
6510     * @hide
6511     */
6512    @Override
6513    public void dispatchFinishTemporaryDetach() {
6514        mDispatchTemporaryDetach = true;
6515        super.dispatchFinishTemporaryDetach();
6516        mDispatchTemporaryDetach = false;
6517    }
6518
6519    @Override
6520    public void onStartTemporaryDetach() {
6521        super.onStartTemporaryDetach();
6522        // Only track when onStartTemporaryDetach() is called directly,
6523        // usually because this instance is an editable field in a list
6524        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
6525    }
6526
6527    @Override
6528    public void onFinishTemporaryDetach() {
6529        super.onFinishTemporaryDetach();
6530        // Only track when onStartTemporaryDetach() is called directly,
6531        // usually because this instance is an editable field in a list
6532        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
6533    }
6534
6535    @Override
6536    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
6537        if (mTemporaryDetach) {
6538            // If we are temporarily in the detach state, then do nothing.
6539            super.onFocusChanged(focused, direction, previouslyFocusedRect);
6540            return;
6541        }
6542
6543        mShowCursor = SystemClock.uptimeMillis();
6544
6545        ensureEndedBatchEdit();
6546
6547        if (focused) {
6548            int selStart = getSelectionStart();
6549            int selEnd = getSelectionEnd();
6550
6551            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
6552                // Has to be done before onTakeFocus, which can be overloaded.
6553                if (mLastTouchOffset >= 0) {
6554                    // Can happen when a TextView is displayed after its content has been deleted.
6555                    mLastTouchOffset = Math.min(mLastTouchOffset, mText.length());
6556                    Selection.setSelection((Spannable) mText, mLastTouchOffset);
6557                }
6558
6559                if (mMovement != null) {
6560                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
6561                }
6562
6563                if (mSelectAllOnFocus) {
6564                    Selection.setSelection((Spannable) mText, 0, mText.length());
6565                }
6566
6567                // The DecorView does not have focus when the 'Done' ExtractEditText button is
6568                // pressed. Since it is the ViewRoot's mView, it requests focus before
6569                // ExtractEditText clears focus, which gives focus to the ExtractEditText.
6570                // This special case ensure that we keep current selection in that case.
6571                // It would be better to know why the DecorView does not have focus at that time.
6572                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
6573                        selStart >= 0 && selEnd >= 0) {
6574                    /*
6575                     * Someone intentionally set the selection, so let them
6576                     * do whatever it is that they wanted to do instead of
6577                     * the default on-focus behavior.  We reset the selection
6578                     * here instead of just skipping the onTakeFocus() call
6579                     * because some movement methods do something other than
6580                     * just setting the selection in theirs and we still
6581                     * need to go through that path.
6582                     */
6583                    Selection.setSelection((Spannable) mText, selStart, selEnd);
6584                }
6585                mTouchFocusSelected = true;
6586            }
6587
6588            mFrozenWithFocus = false;
6589            mSelectionMoved = false;
6590
6591            if (mText instanceof Spannable) {
6592                Spannable sp = (Spannable) mText;
6593                MetaKeyKeyListener.resetMetaState(sp);
6594            }
6595
6596            makeBlink();
6597
6598            if (mError != null) {
6599                showError();
6600            }
6601        } else {
6602            if (mError != null) {
6603                hideError();
6604            }
6605            // Don't leave us in the middle of a batch edit.
6606            onEndBatchEdit();
6607
6608            hideInsertionPointCursorController();
6609            if (this instanceof ExtractEditText) {
6610                // terminateTextSelectionMode would remove selection, which we want to keep when
6611                // ExtractEditText goes out of focus.
6612                mIsInTextSelectionMode = false;
6613            } else {
6614                terminateTextSelectionMode();
6615            }
6616
6617            mLastTouchOffset = -1;
6618        }
6619
6620        startStopMarquee(focused);
6621
6622        if (mTransformation != null) {
6623            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
6624        }
6625
6626        super.onFocusChanged(focused, direction, previouslyFocusedRect);
6627    }
6628
6629    @Override
6630    public void onWindowFocusChanged(boolean hasWindowFocus) {
6631        super.onWindowFocusChanged(hasWindowFocus);
6632
6633        if (hasWindowFocus) {
6634            if (mBlink != null) {
6635                mBlink.uncancel();
6636
6637                if (isFocused()) {
6638                    mShowCursor = SystemClock.uptimeMillis();
6639                    makeBlink();
6640                }
6641            }
6642        } else {
6643            if (mBlink != null) {
6644                mBlink.cancel();
6645            }
6646            // Don't leave us in the middle of a batch edit.
6647            onEndBatchEdit();
6648            if (mInputContentType != null) {
6649                mInputContentType.enterDown = false;
6650            }
6651            hideInsertionPointCursorController();
6652            if (mSelectionModifierCursorController != null) {
6653                mSelectionModifierCursorController.hide();
6654            }
6655        }
6656
6657        startStopMarquee(hasWindowFocus);
6658    }
6659
6660    @Override
6661    protected void onVisibilityChanged(View changedView, int visibility) {
6662        super.onVisibilityChanged(changedView, visibility);
6663        if (visibility != VISIBLE) {
6664            hideInsertionPointCursorController();
6665            if (mSelectionModifierCursorController != null) {
6666                mSelectionModifierCursorController.hide();
6667            }
6668        }
6669    }
6670
6671    /**
6672     * Use {@link BaseInputConnection#removeComposingSpans
6673     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
6674     * state from this text view.
6675     */
6676    public void clearComposingText() {
6677        if (mText instanceof Spannable) {
6678            BaseInputConnection.removeComposingSpans((Spannable)mText);
6679        }
6680    }
6681
6682    @Override
6683    public void setSelected(boolean selected) {
6684        boolean wasSelected = isSelected();
6685
6686        super.setSelected(selected);
6687
6688        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6689            if (selected) {
6690                startMarquee();
6691            } else {
6692                stopMarquee();
6693            }
6694        }
6695    }
6696
6697    private void onTapUpEvent(int prevStart, int prevEnd) {
6698        final int start = getSelectionStart();
6699        final int end = getSelectionEnd();
6700
6701        if (start == end) {
6702            if (start >= prevStart && start < prevEnd) {
6703                // Restore previous selection
6704                Selection.setSelection((Spannable)mText, prevStart, prevEnd);
6705
6706                if (mSelectionModifierCursorController != null &&
6707                        !mSelectionModifierCursorController.isShowing()) {
6708                    // If the anchors aren't showing, revive them.
6709                    mSelectionModifierCursorController.show();
6710                } else {
6711                    // Tapping inside the selection displays the cut/copy/paste context menu
6712                    // as long as the anchors are already showing.
6713                    showContextMenu();
6714                }
6715                return;
6716            } else {
6717                // Tapping outside stops selection mode, if any
6718                stopTextSelectionMode();
6719
6720                if (mInsertionPointCursorController != null) {
6721                    mInsertionPointCursorController.show();
6722                }
6723            }
6724        } else if (hasSelection() && mSelectionModifierCursorController != null) {
6725            mSelectionModifierCursorController.show();
6726        }
6727    }
6728
6729    class CommitSelectionReceiver extends ResultReceiver {
6730        private final int mPrevStart, mPrevEnd;
6731
6732        public CommitSelectionReceiver(int prevStart, int prevEnd) {
6733            super(getHandler());
6734            mPrevStart = prevStart;
6735            mPrevEnd = prevEnd;
6736        }
6737
6738        @Override
6739        protected void onReceiveResult(int resultCode, Bundle resultData) {
6740            // If this tap was actually used to show the IMM, leave cursor or selection unchanged
6741            // by restoring its previous position.
6742            if (resultCode == InputMethodManager.RESULT_SHOWN) {
6743                final int len = mText.length();
6744                int start = Math.min(len, mPrevStart);
6745                int end = Math.min(len, mPrevEnd);
6746                Selection.setSelection((Spannable)mText, start, end);
6747
6748                if (hasSelection()) {
6749                    startTextSelectionMode();
6750                }
6751            }
6752        }
6753    }
6754
6755    @Override
6756    public boolean onTouchEvent(MotionEvent event) {
6757        final int action = event.getActionMasked();
6758        if (action == MotionEvent.ACTION_DOWN) {
6759            if (mInsertionPointCursorController != null) {
6760                mInsertionPointCursorController.onTouchEvent(event);
6761            }
6762            if (mSelectionModifierCursorController != null) {
6763                mSelectionModifierCursorController.onTouchEvent(event);
6764            }
6765
6766            // Reset this state; it will be re-set if super.onTouchEvent
6767            // causes focus to move to the view.
6768            mTouchFocusSelected = false;
6769            mScrolled = false;
6770        }
6771
6772        final boolean superResult = super.onTouchEvent(event);
6773
6774        /*
6775         * Don't handle the release after a long press, because it will
6776         * move the selection away from whatever the menu action was
6777         * trying to affect.
6778         */
6779        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
6780            mEatTouchRelease = false;
6781            return superResult;
6782        }
6783
6784        if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
6785            if (mInsertionPointCursorController != null) {
6786                mInsertionPointCursorController.onTouchEvent(event);
6787            }
6788            if (mSelectionModifierCursorController != null) {
6789                mSelectionModifierCursorController.onTouchEvent(event);
6790            }
6791
6792            boolean handled = false;
6793
6794            // Save previous selection, in case this event is used to show the IME.
6795            int oldSelStart = getSelectionStart();
6796            int oldSelEnd = getSelectionEnd();
6797
6798            final int oldScrollX = mScrollX;
6799            final int oldScrollY = mScrollY;
6800
6801            if (mMovement != null) {
6802                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
6803            }
6804
6805            if (isTextEditable()) {
6806                if (mScrollX != oldScrollX || mScrollY != oldScrollY) {
6807                    // Hide insertion anchor while scrolling. Leave selection.
6808                    hideInsertionPointCursorController();
6809                    if (mSelectionModifierCursorController != null &&
6810                            mSelectionModifierCursorController.isShowing()) {
6811                        mSelectionModifierCursorController.updatePosition();
6812                    }
6813                }
6814                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
6815                    InputMethodManager imm = (InputMethodManager)
6816                          getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
6817
6818                    CommitSelectionReceiver csr = null;
6819                    if (getSelectionStart() != oldSelStart || getSelectionEnd() != oldSelEnd ||
6820                            didTouchFocusSelect()) {
6821                        csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd);
6822                    }
6823
6824                    handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
6825
6826                    // Cannot be done by CommitSelectionReceiver, which might not always be called,
6827                    // for instance when dealing with an ExtractEditText.
6828                    onTapUpEvent(oldSelStart, oldSelEnd);
6829                }
6830            }
6831
6832            if (handled) {
6833                return true;
6834            }
6835        }
6836
6837        return superResult;
6838    }
6839
6840    private void prepareCursorControllers() {
6841        // TODO Add an extra android:cursorController flag to disable the controller?
6842        if (mCursorVisible && mLayout != null) {
6843            if (mInsertionPointCursorController == null) {
6844                mInsertionPointCursorController = new InsertionPointCursorController();
6845            }
6846        } else {
6847            mInsertionPointCursorController = null;
6848        }
6849
6850        if (textCanBeSelected() && mLayout != null) {
6851            if (mSelectionModifierCursorController == null) {
6852                mSelectionModifierCursorController = new SelectionModifierCursorController();
6853            }
6854        } else {
6855            // Stop selection mode if the controller becomes unavailable.
6856            stopTextSelectionMode();
6857            mSelectionModifierCursorController = null;
6858        }
6859    }
6860
6861    /**
6862     * @return True iff this TextView contains a text that can be edited.
6863     */
6864    private boolean isTextEditable() {
6865        return mText instanceof Editable && onCheckIsTextEditor();
6866    }
6867
6868    /**
6869     * Returns true, only while processing a touch gesture, if the initial
6870     * touch down event caused focus to move to the text view and as a result
6871     * its selection changed.  Only valid while processing the touch gesture
6872     * of interest.
6873     */
6874    public boolean didTouchFocusSelect() {
6875        return mTouchFocusSelected;
6876    }
6877
6878    @Override
6879    public void cancelLongPress() {
6880        super.cancelLongPress();
6881        mScrolled = true;
6882    }
6883
6884    @Override
6885    public boolean onTrackballEvent(MotionEvent event) {
6886        if (mMovement != null && mText instanceof Spannable &&
6887            mLayout != null) {
6888            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
6889                return true;
6890            }
6891        }
6892
6893        return super.onTrackballEvent(event);
6894    }
6895
6896    public void setScroller(Scroller s) {
6897        mScroller = s;
6898    }
6899
6900    private static class Blink extends Handler implements Runnable {
6901        private final WeakReference<TextView> mView;
6902        private boolean mCancelled;
6903
6904        public Blink(TextView v) {
6905            mView = new WeakReference<TextView>(v);
6906        }
6907
6908        public void run() {
6909            if (mCancelled) {
6910                return;
6911            }
6912
6913            removeCallbacks(Blink.this);
6914
6915            TextView tv = mView.get();
6916
6917            if (tv != null && tv.isFocused()) {
6918                int st = tv.getSelectionStart();
6919                int en = tv.getSelectionEnd();
6920
6921                if (st == en && st >= 0 && en >= 0) {
6922                    if (tv.mLayout != null) {
6923                        tv.invalidateCursorPath();
6924                    }
6925
6926                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
6927                }
6928            }
6929        }
6930
6931        void cancel() {
6932            if (!mCancelled) {
6933                removeCallbacks(Blink.this);
6934                mCancelled = true;
6935            }
6936        }
6937
6938        void uncancel() {
6939            mCancelled = false;
6940        }
6941    }
6942
6943    @Override
6944    protected float getLeftFadingEdgeStrength() {
6945        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6946            if (mMarquee != null && !mMarquee.isStopped()) {
6947                final Marquee marquee = mMarquee;
6948                if (marquee.shouldDrawLeftFade()) {
6949                    return marquee.mScroll / getHorizontalFadingEdgeLength();
6950                } else {
6951                    return 0.0f;
6952                }
6953            } else if (getLineCount() == 1) {
6954                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6955                    case Gravity.LEFT:
6956                        return 0.0f;
6957                    case Gravity.RIGHT:
6958                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
6959                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
6960                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
6961                    case Gravity.CENTER_HORIZONTAL:
6962                        return 0.0f;
6963                }
6964            }
6965        }
6966        return super.getLeftFadingEdgeStrength();
6967    }
6968
6969    @Override
6970    protected float getRightFadingEdgeStrength() {
6971        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6972            if (mMarquee != null && !mMarquee.isStopped()) {
6973                final Marquee marquee = mMarquee;
6974                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
6975            } else if (getLineCount() == 1) {
6976                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6977                    case Gravity.LEFT:
6978                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
6979                                getCompoundPaddingRight();
6980                        final float lineWidth = mLayout.getLineWidth(0);
6981                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
6982                    case Gravity.RIGHT:
6983                        return 0.0f;
6984                    case Gravity.CENTER_HORIZONTAL:
6985                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
6986                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
6987                                getHorizontalFadingEdgeLength();
6988                }
6989            }
6990        }
6991        return super.getRightFadingEdgeStrength();
6992    }
6993
6994    @Override
6995    protected int computeHorizontalScrollRange() {
6996        if (mLayout != null)
6997            return mLayout.getWidth();
6998
6999        return super.computeHorizontalScrollRange();
7000    }
7001
7002    @Override
7003    protected int computeVerticalScrollRange() {
7004        if (mLayout != null)
7005            return mLayout.getHeight();
7006
7007        return super.computeVerticalScrollRange();
7008    }
7009
7010    @Override
7011    protected int computeVerticalScrollExtent() {
7012        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7013    }
7014
7015    public enum BufferType {
7016        NORMAL, SPANNABLE, EDITABLE,
7017    }
7018
7019    /**
7020     * Returns the TextView_textColor attribute from the
7021     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7022     * from the TextView_textAppearance attribute, if TextView_textColor
7023     * was not set directly.
7024     */
7025    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7026        ColorStateList colors;
7027        colors = attrs.getColorStateList(com.android.internal.R.styleable.
7028                                         TextView_textColor);
7029
7030        if (colors == null) {
7031            int ap = attrs.getResourceId(com.android.internal.R.styleable.
7032                                         TextView_textAppearance, -1);
7033            if (ap != -1) {
7034                TypedArray appearance;
7035                appearance = context.obtainStyledAttributes(ap,
7036                                            com.android.internal.R.styleable.TextAppearance);
7037                colors = appearance.getColorStateList(com.android.internal.R.styleable.
7038                                                  TextAppearance_textColor);
7039                appearance.recycle();
7040            }
7041        }
7042
7043        return colors;
7044    }
7045
7046    /**
7047     * Returns the default color from the TextView_textColor attribute
7048     * from the AttributeSet, if set, or the default color from the
7049     * TextAppearance_textColor from the TextView_textAppearance attribute,
7050     * if TextView_textColor was not set directly.
7051     */
7052    public static int getTextColor(Context context,
7053                                   TypedArray attrs,
7054                                   int def) {
7055        ColorStateList colors = getTextColors(context, attrs);
7056
7057        if (colors == null) {
7058            return def;
7059        } else {
7060            return colors.getDefaultColor();
7061        }
7062    }
7063
7064    @Override
7065    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
7066        switch (keyCode) {
7067        case KeyEvent.KEYCODE_A:
7068            if (canSelectText()) {
7069                return onTextContextMenuItem(ID_SELECT_ALL);
7070            }
7071
7072            break;
7073
7074        case KeyEvent.KEYCODE_X:
7075            if (canCut()) {
7076                return onTextContextMenuItem(ID_CUT);
7077            }
7078
7079            break;
7080
7081        case KeyEvent.KEYCODE_C:
7082            if (canCopy()) {
7083                return onTextContextMenuItem(ID_COPY);
7084            }
7085
7086            break;
7087
7088        case KeyEvent.KEYCODE_V:
7089            if (canPaste()) {
7090                return onTextContextMenuItem(ID_PASTE);
7091            }
7092
7093            break;
7094        }
7095
7096        return super.onKeyShortcut(keyCode, event);
7097    }
7098
7099    private boolean canSelectText() {
7100        return textCanBeSelected() && mText.length() != 0;
7101    }
7102
7103    private boolean textCanBeSelected() {
7104        // prepareCursorController() relies on this method.
7105        // If you change this condition, make sure prepareCursorController is called anywhere
7106        // the value of this condition might be changed.
7107        return (mText instanceof Spannable &&
7108                mMovement != null &&
7109                mMovement.canSelectArbitrarily());
7110    }
7111
7112    private boolean canCut() {
7113        if (mTransformation instanceof PasswordTransformationMethod) {
7114            return false;
7115        }
7116
7117        if (mText.length() > 0 && hasSelection()) {
7118            if (mText instanceof Editable && mInput != null) {
7119                return true;
7120            }
7121        }
7122
7123        return false;
7124    }
7125
7126    private boolean canCopy() {
7127        if (mTransformation instanceof PasswordTransformationMethod) {
7128            return false;
7129        }
7130
7131        if (mText.length() > 0 && hasSelection()) {
7132            return true;
7133        }
7134
7135        return false;
7136    }
7137
7138    private boolean canPaste() {
7139        return (mText instanceof Editable &&
7140                mInput != null &&
7141                getSelectionStart() >= 0 &&
7142                getSelectionEnd() >= 0 &&
7143                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
7144                hasText());
7145    }
7146
7147    /**
7148     * Returns the offsets delimiting the 'word' located at position offset.
7149     *
7150     * @param offset An offset in the text.
7151     * @return The offsets for the start and end of the word located at <code>offset</code>.
7152     * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
7153     * Returns a negative value if no valid word was found.
7154     */
7155    private long getWordLimitsAt(int offset) {
7156        /*
7157         * Quick return if the input type is one where adding words
7158         * to the dictionary doesn't make any sense.
7159         */
7160        int klass = mInputType & InputType.TYPE_MASK_CLASS;
7161        if (klass == InputType.TYPE_CLASS_NUMBER ||
7162            klass == InputType.TYPE_CLASS_PHONE ||
7163            klass == InputType.TYPE_CLASS_DATETIME) {
7164            return -1;
7165        }
7166
7167        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
7168        if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
7169            variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
7170            variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
7171            variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
7172            variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
7173            return -1;
7174        }
7175
7176        int len = mText.length();
7177        int end = Math.min(offset, len);
7178
7179        if (end < 0) {
7180            return -1;
7181        }
7182
7183        int start = end;
7184
7185        for (; start > 0; start--) {
7186            char c = mTransformed.charAt(start - 1);
7187            int type = Character.getType(c);
7188
7189            if (c != '\'' &&
7190                type != Character.UPPERCASE_LETTER &&
7191                type != Character.LOWERCASE_LETTER &&
7192                type != Character.TITLECASE_LETTER &&
7193                type != Character.MODIFIER_LETTER &&
7194                type != Character.DECIMAL_DIGIT_NUMBER) {
7195                break;
7196            }
7197        }
7198
7199        for (; end < len; end++) {
7200            char c = mTransformed.charAt(end);
7201            int type = Character.getType(c);
7202
7203            if (c != '\'' &&
7204                type != Character.UPPERCASE_LETTER &&
7205                type != Character.LOWERCASE_LETTER &&
7206                type != Character.TITLECASE_LETTER &&
7207                type != Character.MODIFIER_LETTER &&
7208                type != Character.DECIMAL_DIGIT_NUMBER) {
7209                break;
7210            }
7211        }
7212
7213        if (start == end) {
7214            return -1;
7215        }
7216
7217        if (end - start > 48) {
7218            return -1;
7219        }
7220
7221        boolean hasLetter = false;
7222        for (int i = start; i < end; i++) {
7223            if (Character.isLetter(mTransformed.charAt(i))) {
7224                hasLetter = true;
7225                break;
7226            }
7227        }
7228
7229        if (!hasLetter) {
7230            return -1;
7231        }
7232
7233        // Two ints packed in a long
7234        return (((long) start) << 32) | end;
7235    }
7236
7237    private void selectCurrentWord() {
7238        // In case selection mode is started after an orientation change or after a select all,
7239        // use the current selection instead of creating one
7240        if (hasSelection()) {
7241            return;
7242        }
7243
7244        int selectionStart, selectionEnd;
7245
7246        // selectionModifierCursorController is not null at that point
7247        SelectionModifierCursorController selectionModifierCursorController =
7248            ((SelectionModifierCursorController) mSelectionModifierCursorController);
7249        int minOffset = selectionModifierCursorController.getMinTouchOffset();
7250        int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
7251
7252        long wordLimits = getWordLimitsAt(minOffset);
7253        if (wordLimits >= 0) {
7254            selectionStart = (int) (wordLimits >>> 32);
7255        } else {
7256            selectionStart = Math.max(minOffset - 5, 0);
7257        }
7258
7259        wordLimits = getWordLimitsAt(maxOffset);
7260        if (wordLimits >= 0) {
7261            selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
7262        } else {
7263            selectionEnd = Math.min(maxOffset + 5, mText.length());
7264        }
7265
7266        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
7267    }
7268
7269    private String getWordForDictionary() {
7270        if (mLastTouchOffset < 0) {
7271            return null;
7272        }
7273
7274        long wordLimits = getWordLimitsAt(mLastTouchOffset);
7275        if (wordLimits >= 0) {
7276            int start = (int) (wordLimits >>> 32);
7277            int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
7278            return mTransformed.subSequence(start, end).toString();
7279        } else {
7280            return null;
7281        }
7282    }
7283
7284    @Override
7285    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
7286        if (!isShown()) {
7287            return false;
7288        }
7289
7290        final boolean isPassword = isPasswordInputType(mInputType);
7291
7292        if (!isPassword) {
7293            CharSequence text = getText();
7294            if (TextUtils.isEmpty(text)) {
7295                text = getHint();
7296            }
7297            if (!TextUtils.isEmpty(text)) {
7298                if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
7299                    text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
7300                }
7301                event.getText().add(text);
7302            }
7303        } else {
7304            event.setPassword(isPassword);
7305        }
7306        return false;
7307    }
7308
7309    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7310            int fromIndex, int removedCount, int addedCount) {
7311        AccessibilityEvent event =
7312            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7313        event.setFromIndex(fromIndex);
7314        event.setRemovedCount(removedCount);
7315        event.setAddedCount(addedCount);
7316        event.setBeforeText(beforeText);
7317        sendAccessibilityEventUnchecked(event);
7318    }
7319
7320    @Override
7321    protected void onCreateContextMenu(ContextMenu menu) {
7322        super.onCreateContextMenu(menu);
7323        boolean added = false;
7324
7325        if (mIsInTextSelectionMode) {
7326            MenuHandler handler = new MenuHandler();
7327
7328            if (canCut()) {
7329                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
7330                     setOnMenuItemClickListener(handler).
7331                     setAlphabeticShortcut('x');
7332                added = true;
7333            }
7334
7335            if (canCopy()) {
7336                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
7337                     setOnMenuItemClickListener(handler).
7338                     setAlphabeticShortcut('c');
7339                added = true;
7340            }
7341
7342            if (canPaste()) {
7343                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
7344                     setOnMenuItemClickListener(handler).
7345                     setAlphabeticShortcut('v');
7346                added = true;
7347            }
7348        } else {
7349            /*
7350            if (!isFocused()) {
7351                if (isFocusable() && mInput != null) {
7352                    if (canCopy()) {
7353                        MenuHandler handler = new MenuHandler();
7354                        menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
7355                             setOnMenuItemClickListener(handler).
7356                             setAlphabeticShortcut('c');
7357                        menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7358                    }
7359                }
7360
7361                //return;
7362            }
7363             */
7364            MenuHandler handler = new MenuHandler();
7365
7366            if (canSelectText()) {
7367                menu.add(0, ID_START_SELECTING_TEXT, 0, com.android.internal.R.string.selectText).
7368                     setOnMenuItemClickListener(handler);
7369                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
7370                     setOnMenuItemClickListener(handler).
7371                     setAlphabeticShortcut('a');
7372                added = true;
7373            }
7374
7375            if (mText instanceof Spanned) {
7376                int selStart = getSelectionStart();
7377                int selEnd = getSelectionEnd();
7378
7379                int min = Math.min(selStart, selEnd);
7380                int max = Math.max(selStart, selEnd);
7381
7382                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7383                        URLSpan.class);
7384                if (urls.length == 1) {
7385                    menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
7386                         setOnMenuItemClickListener(handler);
7387                    added = true;
7388                }
7389            }
7390
7391            if (canPaste()) {
7392                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
7393                     setOnMenuItemClickListener(handler).
7394                     setAlphabeticShortcut('v');
7395                added = true;
7396            }
7397
7398            if (isInputMethodTarget()) {
7399                menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
7400                     setOnMenuItemClickListener(handler);
7401                added = true;
7402            }
7403
7404            String word = getWordForDictionary();
7405            if (word != null) {
7406                menu.add(1, ID_ADD_TO_DICTIONARY, 0,
7407                     getContext().getString(com.android.internal.R.string.addToDictionary, word)).
7408                     setOnMenuItemClickListener(handler);
7409                added = true;
7410
7411            }
7412        }
7413
7414        if (added) {
7415            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7416        }
7417    }
7418
7419    /**
7420     * Returns whether this text view is a current input method target.  The
7421     * default implementation just checks with {@link InputMethodManager}.
7422     */
7423    public boolean isInputMethodTarget() {
7424        InputMethodManager imm = InputMethodManager.peekInstance();
7425        return imm != null && imm.isActive(this);
7426    }
7427
7428    // Context menu entries
7429    private static final int ID_SELECT_ALL = android.R.id.selectAll;
7430    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
7431    private static final int ID_CUT = android.R.id.cut;
7432    private static final int ID_COPY = android.R.id.copy;
7433    private static final int ID_PASTE = android.R.id.paste;
7434    private static final int ID_COPY_URL = android.R.id.copyUrl;
7435    private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
7436    private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
7437
7438    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
7439        public boolean onMenuItemClick(MenuItem item) {
7440            return onTextContextMenuItem(item.getItemId());
7441        }
7442    }
7443
7444    /**
7445     * Called when a context menu option for the text view is selected.  Currently
7446     * this will be one of: {@link android.R.id#selectAll},
7447     * {@link android.R.id#startSelectingText},
7448     * {@link android.R.id#cut}, {@link android.R.id#copy},
7449     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
7450     * or {@link android.R.id#switchInputMethod}.
7451     */
7452    public boolean onTextContextMenuItem(int id) {
7453        int min = 0;
7454        int max = mText.length();
7455
7456        if (isFocused()) {
7457            final int selStart = getSelectionStart();
7458            final int selEnd = getSelectionEnd();
7459
7460            min = Math.max(0, Math.min(selStart, selEnd));
7461            max = Math.max(0, Math.max(selStart, selEnd));
7462        }
7463
7464        ClipboardManager clip = (ClipboardManager)getContext()
7465                .getSystemService(Context.CLIPBOARD_SERVICE);
7466
7467        switch (id) {
7468            case ID_SELECT_ALL:
7469                Selection.setSelection((Spannable) mText, 0, mText.length());
7470                startTextSelectionMode();
7471                return true;
7472
7473            case ID_START_SELECTING_TEXT:
7474                startTextSelectionMode();
7475                return true;
7476
7477            case ID_CUT:
7478                clip.setText(mTransformed.subSequence(min, max));
7479                ((Editable) mText).delete(min, max);
7480                stopTextSelectionMode();
7481                return true;
7482
7483            case ID_COPY:
7484                clip.setText(mTransformed.subSequence(min, max));
7485                stopTextSelectionMode();
7486                return true;
7487
7488            case ID_PASTE:
7489                CharSequence paste = clip.getText();
7490
7491                if (paste != null && paste.length() > 0) {
7492                    // Paste adds/removes spaces before or after insertion as needed.
7493
7494                    if (Character.isSpaceChar(paste.charAt(0))) {
7495                        if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
7496                            // Two spaces at beginning of paste: remove one
7497                            final int originalLength = mText.length();
7498                            ((Editable) mText).replace(min - 1, min, "");
7499                            // Due to filters, there is no garantee that exactly one character was
7500                            // removed. Count instead.
7501                            final int delta = mText.length() - originalLength;
7502                            min += delta;
7503                            max += delta;
7504                        }
7505                    } else {
7506                        if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
7507                            // No space at beginning of paste: add one
7508                            final int originalLength = mText.length();
7509                            ((Editable) mText).replace(min, min, " ");
7510                            // Taking possible filters into account as above.
7511                            final int delta = mText.length() - originalLength;
7512                            min += delta;
7513                            max += delta;
7514                        }
7515                    }
7516
7517                    if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
7518                        if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
7519                            // Two spaces at end of paste: remove one
7520                            ((Editable) mText).replace(max, max + 1, "");
7521                        }
7522                    } else {
7523                        if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
7524                            // No space at end of paste: add one
7525                            ((Editable) mText).replace(max, max, " ");
7526                        }
7527                    }
7528
7529                    Selection.setSelection((Spannable) mText, max);
7530                    ((Editable) mText).replace(min, max, paste);
7531                    stopTextSelectionMode();
7532                }
7533                return true;
7534
7535            case ID_COPY_URL:
7536                URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
7537                if (urls.length == 1) {
7538                    clip.setText(urls[0].getURL());
7539                }
7540                return true;
7541
7542            case ID_SWITCH_INPUT_METHOD:
7543                InputMethodManager imm = InputMethodManager.peekInstance();
7544                if (imm != null) {
7545                    imm.showInputMethodPicker();
7546                }
7547                return true;
7548
7549            case ID_ADD_TO_DICTIONARY:
7550                String word = getWordForDictionary();
7551
7552                if (word != null) {
7553                    Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
7554                    i.putExtra("word", word);
7555                    i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
7556                    getContext().startActivity(i);
7557                }
7558                return true;
7559            }
7560
7561        return false;
7562    }
7563
7564    @Override
7565    public boolean performLongClick() {
7566        if (super.performLongClick()) {
7567            mEatTouchRelease = true;
7568            return true;
7569        }
7570
7571        return false;
7572    }
7573
7574    private void startTextSelectionMode() {
7575        if (!mIsInTextSelectionMode) {
7576            if (mSelectionModifierCursorController == null) {
7577                Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
7578                return;
7579            }
7580
7581            if (!requestFocus()) {
7582                return;
7583            }
7584
7585            selectCurrentWord();
7586            mSelectionModifierCursorController.show();
7587            mIsInTextSelectionMode = true;
7588        }
7589    }
7590
7591    /**
7592     * Same as {@link #stopTextSelectionMode()}, except that there is no cursor controller
7593     * fade out animation. Needed since the drawable and their alpha values are shared by all
7594     * TextViews. Switching from one TextView to another would fade the cursor controllers in the
7595     * new one otherwise.
7596     */
7597    private void terminateTextSelectionMode() {
7598        stopTextSelectionMode();
7599        if (mSelectionModifierCursorController != null) {
7600            SelectionModifierCursorController selectionModifierCursorController =
7601                (SelectionModifierCursorController) mSelectionModifierCursorController;
7602            selectionModifierCursorController.cancelFadeOutAnimation();
7603        }
7604    }
7605
7606    private void stopTextSelectionMode() {
7607        if (mIsInTextSelectionMode) {
7608            Selection.setSelection((Spannable) mText, getSelectionEnd());
7609            if (mSelectionModifierCursorController != null) {
7610                mSelectionModifierCursorController.hide();
7611            }
7612
7613            mIsInTextSelectionMode = false;
7614        }
7615    }
7616
7617    /**
7618     * A CursorController instance can be used to control a cursor in the text.
7619     * It is not used outside of {@link TextView}.
7620     * @hide
7621     */
7622    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
7623        /**
7624         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
7625         * See also {@link #hide()}.
7626         */
7627        public void show();
7628
7629        /**
7630         * Hide the cursor controller from screen.
7631         * See also {@link #show()}.
7632         */
7633        public void hide();
7634
7635        /**
7636         * @return true if the CursorController is currently visible
7637         */
7638        public boolean isShowing();
7639
7640        /**
7641         * Update the controller's position.
7642         */
7643        public void updatePosition(HandleView handle, int x, int y);
7644
7645        public void updatePosition();
7646
7647        /**
7648         * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
7649         * a chance to become active and/or visible.
7650         * @param event The touch event
7651         */
7652        public boolean onTouchEvent(MotionEvent event);
7653    }
7654
7655    private class HandleView extends View {
7656        private boolean mPositionOnTop = false;
7657        private Drawable mDrawable;
7658        private PopupWindow mContainer;
7659        private int mPositionX;
7660        private int mPositionY;
7661        private CursorController mController;
7662        private boolean mIsDragging;
7663        private float mTouchToWindowOffsetX;
7664        private float mTouchToWindowOffsetY;
7665        private float mHotspotX;
7666        private float mHotspotY;
7667        private int mHeight;
7668        private float mTouchOffsetY;
7669        private int mLastParentX;
7670        private int mLastParentY;
7671
7672        public static final int LEFT = 0;
7673        public static final int CENTER = 1;
7674        public static final int RIGHT = 2;
7675
7676        public HandleView(CursorController controller, int pos) {
7677            super(TextView.this.mContext);
7678            mController = controller;
7679            mContainer = new PopupWindow(TextView.this.mContext, null,
7680                    com.android.internal.R.attr.textSelectHandleWindowStyle);
7681            mContainer.setSplitTouchEnabled(true);
7682            mContainer.setClippingEnabled(false);
7683
7684            setOrientation(pos);
7685        }
7686
7687        public void setOrientation(int pos) {
7688            int handleWidth;
7689            switch (pos) {
7690            case LEFT: {
7691                if (mSelectHandleLeft == null) {
7692                    mSelectHandleLeft = mContext.getResources().getDrawable(
7693                            mTextSelectHandleLeftRes);
7694                }
7695                mDrawable = mSelectHandleLeft;
7696                handleWidth = mDrawable.getIntrinsicWidth();
7697                mHotspotX = handleWidth / 4 * 3;
7698                break;
7699            }
7700
7701            case RIGHT: {
7702                if (mSelectHandleRight == null) {
7703                    mSelectHandleRight = mContext.getResources().getDrawable(
7704                            mTextSelectHandleRightRes);
7705                }
7706                mDrawable = mSelectHandleRight;
7707                handleWidth = mDrawable.getIntrinsicWidth();
7708                mHotspotX = handleWidth / 4;
7709                break;
7710            }
7711
7712            case CENTER:
7713            default: {
7714                if (mSelectHandleCenter == null) {
7715                    mSelectHandleCenter = mContext.getResources().getDrawable(
7716                            mTextSelectHandleRes);
7717                }
7718                mDrawable = mSelectHandleCenter;
7719                handleWidth = mDrawable.getIntrinsicWidth();
7720                mHotspotX = handleWidth / 2;
7721                break;
7722            }
7723            }
7724
7725            final int handleHeight = mDrawable.getIntrinsicHeight();
7726
7727            mTouchOffsetY = -handleHeight * 0.3f;
7728            mHotspotY = 0;
7729            mHeight = handleHeight;
7730            invalidate();
7731        }
7732
7733        @Override
7734        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7735            setMeasuredDimension(mDrawable.getIntrinsicWidth(),
7736                    mDrawable.getIntrinsicHeight());
7737        }
7738
7739        public void show() {
7740            if (!isPositionVisible()) {
7741                hide();
7742                return;
7743            }
7744            mContainer.setContentView(this);
7745            final int[] coords = mTempCoords;
7746            TextView.this.getLocationInWindow(coords);
7747            coords[0] += mPositionX;
7748            coords[1] += mPositionY;
7749            mContainer.showAtLocation(TextView.this, 0, coords[0], coords[1]);
7750        }
7751
7752        public void hide() {
7753            mIsDragging = false;
7754            mContainer.dismiss();
7755        }
7756
7757        public boolean isShowing() {
7758            return mContainer.isShowing();
7759        }
7760
7761        private boolean isPositionVisible() {
7762            // Always show a dragging handle.
7763            if (mIsDragging) {
7764                return true;
7765            }
7766
7767            final int extendedPaddingTop = getExtendedPaddingTop();
7768            final int extendedPaddingBottom = getExtendedPaddingBottom();
7769            final int compoundPaddingLeft = getCompoundPaddingLeft();
7770            final int compoundPaddingRight = getCompoundPaddingRight();
7771
7772            final TextView hostView = TextView.this;
7773            final int left = 0;
7774            final int right = hostView.getWidth();
7775            final int top = 0;
7776            final int bottom = hostView.getHeight();
7777
7778            if (mTempRect == null) {
7779                mTempRect = new Rect();
7780            }
7781            final Rect clip = mTempRect;
7782            clip.left = left + compoundPaddingLeft;
7783            clip.top = top + extendedPaddingTop;
7784            clip.right = right - compoundPaddingRight;
7785            clip.bottom = bottom - extendedPaddingBottom;
7786
7787            final ViewParent parent = hostView.getParent();
7788            if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
7789                return false;
7790            }
7791
7792            final int[] coords = mTempCoords;
7793            hostView.getLocationInWindow(coords);
7794            final int posX = coords[0] + mPositionX + (int) mHotspotX;
7795            final int posY = coords[1] + mPositionY + (int) mHotspotY;
7796
7797            return posX >= clip.left && posX <= clip.right &&
7798                    posY >= clip.top && posY <= clip.bottom;
7799        }
7800
7801        private void moveTo(int x, int y) {
7802            mPositionX = x - TextView.this.mScrollX;
7803            mPositionY = y - TextView.this.mScrollY;
7804            if (isPositionVisible()) {
7805                int[] coords = null;
7806                if (mContainer.isShowing()){
7807                    coords = mTempCoords;
7808                    TextView.this.getLocationInWindow(coords);
7809                    mContainer.update(coords[0] + mPositionX, coords[1] + mPositionY,
7810                            mRight - mLeft, mBottom - mTop);
7811                } else {
7812                    show();
7813                }
7814
7815                if (mIsDragging) {
7816                    if (coords == null) {
7817                        coords = mTempCoords;
7818                        TextView.this.getLocationInWindow(coords);
7819                    }
7820                    if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
7821                        mTouchToWindowOffsetX += coords[0] - mLastParentX;
7822                        mTouchToWindowOffsetY += coords[1] - mLastParentY;
7823                        mLastParentX = coords[0];
7824                        mLastParentY = coords[1];
7825                    }
7826                }
7827            } else {
7828                hide();
7829            }
7830        }
7831
7832        @Override
7833        public void onDraw(Canvas c) {
7834            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
7835            if (mPositionOnTop) {
7836                c.save();
7837                c.rotate(180, (mRight - mLeft) / 2, (mBottom - mTop) / 2);
7838                mDrawable.draw(c);
7839                c.restore();
7840            } else {
7841                mDrawable.draw(c);
7842            }
7843        }
7844
7845        @Override
7846        public boolean onTouchEvent(MotionEvent ev) {
7847            switch (ev.getActionMasked()) {
7848            case MotionEvent.ACTION_DOWN: {
7849                final float rawX = ev.getRawX();
7850                final float rawY = ev.getRawY();
7851                mTouchToWindowOffsetX = rawX - mPositionX;
7852                mTouchToWindowOffsetY = rawY - mPositionY;
7853                final int[] coords = mTempCoords;
7854                TextView.this.getLocationInWindow(coords);
7855                mLastParentX = coords[0];
7856                mLastParentY = coords[1];
7857                mIsDragging = true;
7858                break;
7859            }
7860            case MotionEvent.ACTION_MOVE: {
7861                final float rawX = ev.getRawX();
7862                final float rawY = ev.getRawY();
7863                final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
7864                final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;
7865
7866                mController.updatePosition(this, (int) Math.round(newPosX),
7867                        (int) Math.round(newPosY));
7868
7869                break;
7870            }
7871            case MotionEvent.ACTION_UP:
7872            case MotionEvent.ACTION_CANCEL:
7873                mIsDragging = false;
7874            }
7875            return true;
7876        }
7877
7878        public boolean isDragging() {
7879            return mIsDragging;
7880        }
7881
7882        void positionAtCursor(final int offset, boolean bottom) {
7883            final int width = mDrawable.getIntrinsicWidth();
7884            final int height = mDrawable.getIntrinsicHeight();
7885            final int line = mLayout.getLineForOffset(offset);
7886            final int lineTop = mLayout.getLineTop(line);
7887            final int lineBottom = mLayout.getLineBottom(line);
7888
7889            final Rect bounds = sCursorControllerTempRect;
7890            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - mHotspotX)
7891                + TextView.this.mScrollX;
7892            bounds.top = (bottom ? lineBottom : lineTop - mHeight) + TextView.this.mScrollY;
7893
7894            bounds.right = bounds.left + width;
7895            bounds.bottom = bounds.top + height;
7896
7897            convertFromViewportToContentCoordinates(bounds);
7898            moveTo(bounds.left, bounds.top);
7899        }
7900    }
7901
7902    private class InsertionPointCursorController implements CursorController {
7903        private static final int DELAY_BEFORE_FADE_OUT = 4100;
7904
7905        // The cursor controller image
7906        private final HandleView mHandle;
7907
7908        private final Runnable mHider = new Runnable() {
7909            public void run() {
7910                hide();
7911            }
7912        };
7913
7914        InsertionPointCursorController() {
7915            mHandle = new HandleView(this, HandleView.CENTER);
7916        }
7917
7918        public void show() {
7919            updatePosition();
7920            mHandle.show();
7921            hideDelayed(DELAY_BEFORE_FADE_OUT);
7922        }
7923
7924        public void hide() {
7925            mHandle.hide();
7926            TextView.this.removeCallbacks(mHider);
7927        }
7928
7929        private void hideDelayed(int msec) {
7930            TextView.this.removeCallbacks(mHider);
7931            TextView.this.postDelayed(mHider, msec);
7932        }
7933
7934        public boolean isShowing() {
7935            return mHandle.isShowing();
7936        }
7937
7938        public void updatePosition(HandleView handle, int x, int y) {
7939            final int previousOffset = getSelectionStart();
7940            int offset = getHysteresisOffset(x, y, previousOffset);
7941
7942            if (offset != previousOffset) {
7943                Selection.setSelection((Spannable) mText, offset);
7944                updatePosition();
7945            }
7946            hideDelayed(DELAY_BEFORE_FADE_OUT);
7947        }
7948
7949        public void updatePosition() {
7950            final int offset = getSelectionStart();
7951
7952            if (offset < 0) {
7953                // Should never happen, safety check.
7954                Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
7955                hide();
7956                return;
7957            }
7958
7959            mHandle.positionAtCursor(offset, true);
7960        }
7961
7962        public boolean onTouchEvent(MotionEvent ev) {
7963            return false;
7964        }
7965
7966        public void onTouchModeChanged(boolean isInTouchMode) {
7967            if (!isInTouchMode) {
7968                hide();
7969            }
7970        }
7971    }
7972
7973    private class SelectionModifierCursorController implements CursorController {
7974        // The cursor controller images
7975        private HandleView mStartHandle, mEndHandle;
7976        // The offsets of that last touch down event. Remembered to start selection there.
7977        private int mMinTouchOffset, mMaxTouchOffset;
7978        // Whether selection anchors are active
7979        private boolean mIsShowing;
7980
7981        private static final int DELAY_BEFORE_FADE_OUT = 4100;
7982
7983        private final Runnable mHider = new Runnable() {
7984            public void run() {
7985                hide();
7986            }
7987        };
7988
7989        SelectionModifierCursorController() {
7990            mStartHandle = new HandleView(this, HandleView.LEFT);
7991            mEndHandle = new HandleView(this, HandleView.RIGHT);
7992        }
7993
7994        public void show() {
7995            mIsShowing = true;
7996            updatePosition();
7997            mStartHandle.show();
7998            mEndHandle.show();
7999            hideInsertionPointCursorController();
8000            hideDelayed(DELAY_BEFORE_FADE_OUT);
8001        }
8002
8003        public void hide() {
8004            mStartHandle.hide();
8005            mEndHandle.hide();
8006            mIsShowing = false;
8007            removeCallbacks(mHider);
8008        }
8009
8010        private void hideDelayed(int delay) {
8011            removeCallbacks(mHider);
8012            postDelayed(mHider, delay);
8013        }
8014
8015        public boolean isShowing() {
8016            return mIsShowing;
8017        }
8018
8019        public void cancelFadeOutAnimation() {
8020            hide();
8021        }
8022
8023        public void updatePosition(HandleView handle, int x, int y) {
8024            int selectionStart = getSelectionStart();
8025            int selectionEnd = getSelectionEnd();
8026
8027            final int previousOffset = handle == mStartHandle ? selectionStart : selectionEnd;
8028            int offset = getHysteresisOffset(x, y, previousOffset);
8029
8030            // Handle the case where start and end are swapped, making sure start <= end
8031            if (handle == mStartHandle) {
8032                if (selectionStart == offset || offset > selectionEnd) {
8033                    return; // no change, no need to redraw;
8034                }
8035                // If the user "closes" the selection entirely they were probably trying to
8036                // select a single character. Help them out.
8037                if (offset == selectionEnd) {
8038                    offset = selectionEnd - 1;
8039                }
8040                selectionStart = offset;
8041            } else {
8042                if (selectionEnd == offset || offset < selectionStart) {
8043                    return; // no change, no need to redraw;
8044                }
8045                // If the user "closes" the selection entirely they were probably trying to
8046                // select a single character. Help them out.
8047                if (offset == selectionStart) {
8048                    offset = selectionStart + 1;
8049                }
8050                selectionEnd = offset;
8051            }
8052
8053            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8054            updatePosition();
8055        }
8056
8057        public void updatePosition() {
8058            final int selectionStart = getSelectionStart();
8059            final int selectionEnd = getSelectionEnd();
8060
8061            if ((selectionStart < 0) || (selectionEnd < 0)) {
8062                // Should never happen, safety check.
8063                Log.w(LOG_TAG, "Update selection controller position called with no cursor");
8064                hide();
8065                return;
8066            }
8067
8068            mStartHandle.positionAtCursor(selectionStart, true);
8069            mEndHandle.positionAtCursor(selectionEnd, true);
8070            hideDelayed(DELAY_BEFORE_FADE_OUT);
8071        }
8072
8073        public boolean onTouchEvent(MotionEvent event) {
8074            if (isFocused() && isTextEditable()) {
8075                switch (event.getActionMasked()) {
8076                    case MotionEvent.ACTION_DOWN:
8077                        final int x = (int) event.getX();
8078                        final int y = (int) event.getY();
8079
8080                        // Remember finger down position, to be able to start selection from there
8081                        mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
8082
8083                        break;
8084
8085                    case MotionEvent.ACTION_POINTER_DOWN:
8086                    case MotionEvent.ACTION_POINTER_UP:
8087                        // Handle multi-point gestures. Keep min and max offset positions.
8088                        // Only activated for devices that correctly handle multi-touch.
8089                        if (mContext.getPackageManager().hasSystemFeature(
8090                                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
8091                            updateMinAndMaxOffsets(event);
8092                        }
8093                        break;
8094                }
8095            }
8096            return false;
8097        }
8098
8099        /**
8100         * @param event
8101         */
8102        private void updateMinAndMaxOffsets(MotionEvent event) {
8103            int pointerCount = event.getPointerCount();
8104            for (int index = 0; index < pointerCount; index++) {
8105                final int x = (int) event.getX(index);
8106                final int y = (int) event.getY(index);
8107                int offset = getOffset(x, y);
8108                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
8109                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
8110            }
8111        }
8112
8113        public int getMinTouchOffset() {
8114            return mMinTouchOffset;
8115        }
8116
8117        public int getMaxTouchOffset() {
8118            return mMaxTouchOffset;
8119        }
8120
8121        /**
8122         * @return true iff this controller is currently used to move the selection start.
8123         */
8124        public boolean isSelectionStartDragged() {
8125            return mStartHandle.isDragging();
8126        }
8127
8128        public void onTouchModeChanged(boolean isInTouchMode) {
8129            if (!isInTouchMode) {
8130                hide();
8131            }
8132        }
8133    }
8134
8135    private void hideInsertionPointCursorController() {
8136        if (mInsertionPointCursorController != null) {
8137            mInsertionPointCursorController.hide();
8138        }
8139    }
8140
8141    private void hideControllers() {
8142        hideInsertionPointCursorController();
8143        stopTextSelectionMode();
8144    }
8145
8146    private int getOffsetForHorizontal(int line, int x) {
8147        x -= getTotalPaddingLeft();
8148        // Clamp the position to inside of the view.
8149        x = Math.max(0, x);
8150        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8151        x += getScrollX();
8152        return getLayout().getOffsetForHorizontal(line, x);
8153    }
8154
8155    /**
8156     * Get the offset character closest to the specified absolute position.
8157     *
8158     * @param x The horizontal absolute position of a point on screen
8159     * @param y The vertical absolute position of a point on screen
8160     * @return the character offset for the character whose position is closest to the specified
8161     *  position. Returns -1 if there is no layout.
8162     *
8163     * @hide
8164     */
8165    public int getOffset(int x, int y) {
8166        if (getLayout() == null) return -1;
8167
8168        y -= getTotalPaddingTop();
8169        // Clamp the position to inside of the view.
8170        y = Math.max(0, y);
8171        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8172        y += getScrollY();
8173
8174        final int line = getLayout().getLineForVertical(y);
8175        final int offset = getOffsetForHorizontal(line, x);
8176        return offset;
8177    }
8178
8179    int getHysteresisOffset(int x, int y, int previousOffset) {
8180        final Layout layout = getLayout();
8181        if (layout == null) return -1;
8182
8183        y -= getTotalPaddingTop();
8184        // Clamp the position to inside of the view.
8185        y = Math.max(0, y);
8186        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8187        y += getScrollY();
8188
8189        int line = getLayout().getLineForVertical(y);
8190
8191        final int previousLine = layout.getLineForOffset(previousOffset);
8192        final int previousLineTop = layout.getLineTop(previousLine);
8193        final int previousLineBottom = layout.getLineBottom(previousLine);
8194        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 8;
8195
8196        // If new line is just before or after previous line and y position is less than
8197        // hysteresisThreshold away from previous line, keep cursor on previous line.
8198        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) ||
8199            ((line == previousLine - 1) && ((previousLineTop - y)    < hysteresisThreshold))) {
8200            line = previousLine;
8201        }
8202
8203        return getOffsetForHorizontal(line, x);
8204    }
8205
8206    @ViewDebug.ExportedProperty
8207    private CharSequence            mText;
8208    private CharSequence            mTransformed;
8209    private BufferType              mBufferType = BufferType.NORMAL;
8210
8211    private int                     mInputType = EditorInfo.TYPE_NULL;
8212    private CharSequence            mHint;
8213    private Layout                  mHintLayout;
8214
8215    private KeyListener             mInput;
8216
8217    private MovementMethod          mMovement;
8218    private TransformationMethod    mTransformation;
8219    private ChangeWatcher           mChangeWatcher;
8220
8221    private ArrayList<TextWatcher>  mListeners = null;
8222
8223    // display attributes
8224    private final TextPaint         mTextPaint;
8225    private boolean                 mUserSetTextScaleX;
8226    private final Paint             mHighlightPaint;
8227    private int                     mHighlightColor = 0xCC475925;
8228    private Layout                  mLayout;
8229
8230    private long                    mShowCursor;
8231    private Blink                   mBlink;
8232    private boolean                 mCursorVisible = true;
8233
8234    // Cursor Controllers. Null when disabled.
8235    private CursorController        mInsertionPointCursorController;
8236    private CursorController        mSelectionModifierCursorController;
8237    private boolean                 mIsInTextSelectionMode = false;
8238    private int                     mLastTouchOffset = -1;
8239    // Created once and shared by different CursorController helper methods.
8240    // Only one cursor controller is active at any time which prevent race conditions.
8241    private static Rect             sCursorControllerTempRect = new Rect();
8242
8243    private boolean                 mSelectAllOnFocus = false;
8244
8245    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
8246    private boolean                 mHorizontallyScrolling;
8247
8248    private int                     mAutoLinkMask;
8249    private boolean                 mLinksClickable = true;
8250
8251    private float                   mSpacingMult = 1;
8252    private float                   mSpacingAdd = 0;
8253
8254    private static final int        LINES = 1;
8255    private static final int        EMS = LINES;
8256    private static final int        PIXELS = 2;
8257
8258    private int                     mMaximum = Integer.MAX_VALUE;
8259    private int                     mMaxMode = LINES;
8260    private int                     mMinimum = 0;
8261    private int                     mMinMode = LINES;
8262
8263    private int                     mMaxWidth = Integer.MAX_VALUE;
8264    private int                     mMaxWidthMode = PIXELS;
8265    private int                     mMinWidth = 0;
8266    private int                     mMinWidthMode = PIXELS;
8267
8268    private boolean                 mSingleLine;
8269    private int                     mDesiredHeightAtMeasure = -1;
8270    private boolean                 mIncludePad = true;
8271
8272    // tmp primitives, so we don't alloc them on each draw
8273    private Path                    mHighlightPath;
8274    private boolean                 mHighlightPathBogus = true;
8275    private static final RectF      sTempRect = new RectF();
8276
8277    // XXX should be much larger
8278    private static final int        VERY_WIDE = 16384;
8279
8280    private static final int        BLINK = 500;
8281
8282    private static final int ANIMATED_SCROLL_GAP = 250;
8283    private long mLastScroll;
8284    private Scroller mScroller = null;
8285
8286    private BoringLayout.Metrics mBoring;
8287    private BoringLayout.Metrics mHintBoring;
8288
8289    private BoringLayout mSavedLayout, mSavedHintLayout;
8290
8291    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
8292    private InputFilter[] mFilters = NO_FILTERS;
8293    private static final Spanned EMPTY_SPANNED = new SpannedString("");
8294}
8295