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