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