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