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