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