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