TextView.java revision ed6741817a6c075a306b5e9c63e6c08abc5b8dc6
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    @Override
4149    public boolean isLayoutRtl(Drawable who) {
4150        if (who == null) return false;
4151        if (mDrawables != null) {
4152            final Drawables drawables = mDrawables;
4153            if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight ||
4154                who == drawables.mDrawableTop || who == drawables.mDrawableBottom) {
4155                return isLayoutRtl();
4156            }
4157        }
4158        return super.isLayoutRtl(who);
4159    }
4160
4161    @Override
4162    protected boolean onSetAlpha(int alpha) {
4163        // Alpha is supported if and only if the drawing can be done in one pass.
4164        // TODO text with spans with a background color currently do not respect this alpha.
4165        if (getBackground() == null) {
4166            mCurrentAlpha = alpha;
4167            final Drawables dr = mDrawables;
4168            if (dr != null) {
4169                if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
4170                if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
4171                if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
4172                if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
4173            }
4174            return true;
4175        }
4176
4177        mCurrentAlpha = 255;
4178        return false;
4179    }
4180
4181    /**
4182     * When a TextView is used to display a useful piece of information to the user (such as a
4183     * contact's address), it should be made selectable, so that the user can select and copy this
4184     * content.
4185     *
4186     * Use {@link #setTextIsSelectable(boolean)} or the
4187     * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
4188     * selectable (text is not selectable by default).
4189     *
4190     * Note that the content of an EditText is always selectable.
4191     *
4192     * @return True if the text displayed in this TextView can be selected by the user.
4193     *
4194     * @attr ref android.R.styleable#TextView_textIsSelectable
4195     */
4196    public boolean isTextSelectable() {
4197        return mTextIsSelectable;
4198    }
4199
4200    /**
4201     * Sets whether or not (default) the content of this view is selectable by the user.
4202     *
4203     * Note that this methods affect the {@link #setFocusable(boolean)},
4204     * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
4205     * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
4206     * customized.
4207     *
4208     * See {@link #isTextSelectable} for details.
4209     *
4210     * @param selectable Whether or not the content of this TextView should be selectable.
4211     */
4212    public void setTextIsSelectable(boolean selectable) {
4213        if (mTextIsSelectable == selectable) return;
4214
4215        mTextIsSelectable = selectable;
4216
4217        setFocusableInTouchMode(selectable);
4218        setFocusable(selectable);
4219        setClickable(selectable);
4220        setLongClickable(selectable);
4221
4222        // mInputType is already EditorInfo.TYPE_NULL and mInput is null;
4223
4224        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
4225        setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
4226
4227        // Called by setText above, but safer in case of future code changes
4228        prepareCursorControllers();
4229    }
4230
4231    @Override
4232    protected int[] onCreateDrawableState(int extraSpace) {
4233        final int[] drawableState;
4234
4235        if (mSingleLine) {
4236            drawableState = super.onCreateDrawableState(extraSpace);
4237        } else {
4238            drawableState = super.onCreateDrawableState(extraSpace + 1);
4239            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4240        }
4241
4242        if (mTextIsSelectable) {
4243            // Disable pressed state, which was introduced when TextView was made clickable.
4244            // Prevents text color change.
4245            // setClickable(false) would have a similar effect, but it also disables focus changes
4246            // and long press actions, which are both needed by text selection.
4247            final int length = drawableState.length;
4248            for (int i = 0; i < length; i++) {
4249                if (drawableState[i] == R.attr.state_pressed) {
4250                    final int[] nonPressedState = new int[length - 1];
4251                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4252                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4253                    return nonPressedState;
4254                }
4255            }
4256        }
4257
4258        return drawableState;
4259    }
4260
4261    @Override
4262    protected void onDraw(Canvas canvas) {
4263        if (mPreDrawState == PREDRAW_DONE) {
4264            final ViewTreeObserver observer = getViewTreeObserver();
4265            observer.removeOnPreDrawListener(this);
4266            mPreDrawState = PREDRAW_NOT_REGISTERED;
4267        }
4268
4269        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return;
4270
4271        restartMarqueeIfNeeded();
4272
4273        // Draw the background for this view
4274        super.onDraw(canvas);
4275
4276        final int compoundPaddingLeft = getCompoundPaddingLeft();
4277        final int compoundPaddingTop = getCompoundPaddingTop();
4278        final int compoundPaddingRight = getCompoundPaddingRight();
4279        final int compoundPaddingBottom = getCompoundPaddingBottom();
4280        final int scrollX = mScrollX;
4281        final int scrollY = mScrollY;
4282        final int right = mRight;
4283        final int left = mLeft;
4284        final int bottom = mBottom;
4285        final int top = mTop;
4286
4287        final Drawables dr = mDrawables;
4288        if (dr != null) {
4289            /*
4290             * Compound, not extended, because the icon is not clipped
4291             * if the text height is smaller.
4292             */
4293
4294            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4295            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4296
4297            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4298            // Make sure to update invalidateDrawable() when changing this code.
4299            if (dr.mDrawableLeft != null) {
4300                canvas.save();
4301                canvas.translate(scrollX + mPaddingLeft,
4302                                 scrollY + compoundPaddingTop +
4303                                 (vspace - dr.mDrawableHeightLeft) / 2);
4304                dr.mDrawableLeft.draw(canvas);
4305                canvas.restore();
4306            }
4307
4308            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4309            // Make sure to update invalidateDrawable() when changing this code.
4310            if (dr.mDrawableRight != null) {
4311                canvas.save();
4312                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
4313                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4314                dr.mDrawableRight.draw(canvas);
4315                canvas.restore();
4316            }
4317
4318            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4319            // Make sure to update invalidateDrawable() when changing this code.
4320            if (dr.mDrawableTop != null) {
4321                canvas.save();
4322                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
4323                        scrollY + mPaddingTop);
4324                dr.mDrawableTop.draw(canvas);
4325                canvas.restore();
4326            }
4327
4328            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4329            // Make sure to update invalidateDrawable() when changing this code.
4330            if (dr.mDrawableBottom != null) {
4331                canvas.save();
4332                canvas.translate(scrollX + compoundPaddingLeft +
4333                        (hspace - dr.mDrawableWidthBottom) / 2,
4334                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
4335                dr.mDrawableBottom.draw(canvas);
4336                canvas.restore();
4337            }
4338        }
4339
4340        int color = mCurTextColor;
4341
4342        if (mLayout == null) {
4343            assumeLayout();
4344        }
4345
4346        Layout layout = mLayout;
4347        int cursorcolor = color;
4348
4349        if (mHint != null && mText.length() == 0) {
4350            if (mHintTextColor != null) {
4351                color = mCurHintTextColor;
4352            }
4353
4354            layout = mHintLayout;
4355        }
4356
4357        mTextPaint.setColor(color);
4358        if (mCurrentAlpha != 255) {
4359            // If set, the alpha will override the color's alpha. Multiply the alphas.
4360            mTextPaint.setAlpha((mCurrentAlpha * Color.alpha(color)) / 255);
4361        }
4362        mTextPaint.drawableState = getDrawableState();
4363
4364        canvas.save();
4365        /*  Would be faster if we didn't have to do this. Can we chop the
4366            (displayable) text so that we don't need to do this ever?
4367        */
4368
4369        int extendedPaddingTop = getExtendedPaddingTop();
4370        int extendedPaddingBottom = getExtendedPaddingBottom();
4371
4372        float clipLeft = compoundPaddingLeft + scrollX;
4373        float clipTop = extendedPaddingTop + scrollY;
4374        float clipRight = right - left - compoundPaddingRight + scrollX;
4375        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
4376
4377        if (mShadowRadius != 0) {
4378            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
4379            clipRight += Math.max(0, mShadowDx + mShadowRadius);
4380
4381            clipTop += Math.min(0, mShadowDy - mShadowRadius);
4382            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
4383        }
4384
4385        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4386
4387        int voffsetText = 0;
4388        int voffsetCursor = 0;
4389
4390        // translate in by our padding
4391        {
4392            /* shortcircuit calling getVerticaOffset() */
4393            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4394                voffsetText = getVerticalOffset(false);
4395                voffsetCursor = getVerticalOffset(true);
4396            }
4397            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
4398        }
4399
4400        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
4401        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4402            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
4403                    (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
4404                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
4405                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
4406            }
4407
4408            if (mMarquee != null && mMarquee.isRunning()) {
4409                canvas.translate(-mMarquee.mScroll, 0.0f);
4410            }
4411        }
4412
4413        Path highlight = null;
4414        int selStart = -1, selEnd = -1;
4415        boolean drawCursor = false;
4416
4417        //  If there is no movement method, then there can be no selection.
4418        //  Check that first and attempt to skip everything having to do with
4419        //  the cursor.
4420        //  XXX This is not strictly true -- a program could set the
4421        //  selection manually if it really wanted to.
4422        if (mMovement != null && (isFocused() || isPressed())) {
4423            selStart = getSelectionStart();
4424            selEnd = getSelectionEnd();
4425
4426            if ((isCursorVisible() || mTextIsSelectable) && selStart >= 0 && isEnabled()) {
4427                if (mHighlightPath == null)
4428                    mHighlightPath = new Path();
4429
4430                if (selStart == selEnd) {
4431                    if (!mTextIsSelectable &&
4432                            (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
4433                        if (mHighlightPathBogus) {
4434                            mHighlightPath.reset();
4435                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
4436                            updateCursorsPositions();
4437                            mHighlightPathBogus = false;
4438                        }
4439
4440                        // XXX should pass to skin instead of drawing directly
4441                        mHighlightPaint.setColor(cursorcolor);
4442                        if (mCurrentAlpha != 255) {
4443                            mHighlightPaint.setAlpha(
4444                                    (mCurrentAlpha * Color.alpha(cursorcolor)) / 255);
4445                        }
4446                        mHighlightPaint.setStyle(Paint.Style.STROKE);
4447                        highlight = mHighlightPath;
4448                        drawCursor = mCursorCount > 0;
4449                    }
4450                } else {
4451                    if (mHighlightPathBogus) {
4452                        mHighlightPath.reset();
4453                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4454                        mHighlightPathBogus = false;
4455                    }
4456
4457                    // XXX should pass to skin instead of drawing directly
4458                    mHighlightPaint.setColor(mHighlightColor);
4459                    if (mCurrentAlpha != 255) {
4460                        mHighlightPaint.setAlpha(
4461                                (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
4462                    }
4463                    mHighlightPaint.setStyle(Paint.Style.FILL);
4464
4465                    highlight = mHighlightPath;
4466                }
4467            }
4468        }
4469
4470        /*  Comment out until we decide what to do about animations
4471        boolean isLinearTextOn = false;
4472        if (currentTransformation != null) {
4473            isLinearTextOn = mTextPaint.isLinearTextOn();
4474            Matrix m = currentTransformation.getMatrix();
4475            if (!m.isIdentity()) {
4476                // mTextPaint.setLinearTextOn(true);
4477            }
4478        }
4479        */
4480
4481        final InputMethodState ims = mInputMethodState;
4482        final int cursorOffsetVertical = voffsetCursor - voffsetText;
4483        if (ims != null && ims.mBatchEditNesting == 0) {
4484            InputMethodManager imm = InputMethodManager.peekInstance();
4485            if (imm != null) {
4486                if (imm.isActive(this)) {
4487                    boolean reported = false;
4488                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
4489                        // We are in extract mode and the content has changed
4490                        // in some way... just report complete new text to the
4491                        // input method.
4492                        reported = reportExtractedText();
4493                    }
4494                    if (!reported && highlight != null) {
4495                        int candStart = -1;
4496                        int candEnd = -1;
4497                        if (mText instanceof Spannable) {
4498                            Spannable sp = (Spannable)mText;
4499                            candStart = EditableInputConnection.getComposingSpanStart(sp);
4500                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4501                        }
4502                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4503                    }
4504                }
4505
4506                if (imm.isWatchingCursor(this) && highlight != null) {
4507                    highlight.computeBounds(ims.mTmpRectF, true);
4508                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4509
4510                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
4511                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4512
4513                    ims.mTmpRectF.offset(0, cursorOffsetVertical);
4514
4515                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4516                            (int)(ims.mTmpRectF.top + 0.5),
4517                            (int)(ims.mTmpRectF.right + 0.5),
4518                            (int)(ims.mTmpRectF.bottom + 0.5));
4519
4520                    imm.updateCursor(this,
4521                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4522                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4523                }
4524            }
4525        }
4526
4527        if (mCorrectionHighlighter != null) {
4528            mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
4529        }
4530
4531        if (drawCursor) {
4532            drawCursor(canvas, cursorOffsetVertical);
4533            // Rely on the drawable entirely, do not draw the cursor line.
4534            // Has to be done after the IMM related code above which relies on the highlight.
4535            highlight = null;
4536        }
4537
4538        layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
4539
4540        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
4541            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
4542            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
4543        }
4544
4545        /*  Comment out until we decide what to do about animations
4546        if (currentTransformation != null) {
4547            mTextPaint.setLinearTextOn(isLinearTextOn);
4548        }
4549        */
4550
4551        canvas.restore();
4552    }
4553
4554    private void updateCursorsPositions() {
4555        if (mCursorDrawableRes == 0) {
4556            mCursorCount = 0;
4557            return;
4558        }
4559
4560        final int offset = getSelectionStart();
4561        final int line = mLayout.getLineForOffset(offset);
4562        final int top = mLayout.getLineTop(line);
4563        final int bottom = mLayout.getLineTop(line + 1);
4564
4565        mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
4566
4567        int middle = bottom;
4568        if (mCursorCount == 2) {
4569            // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
4570            middle = (top + bottom) >> 1;
4571        }
4572
4573        updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
4574
4575        if (mCursorCount == 2) {
4576            updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
4577        }
4578    }
4579
4580    private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
4581        if (mCursorDrawable[cursorIndex] == null)
4582            mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
4583
4584        if (mTempRect == null) mTempRect = new Rect();
4585
4586        mCursorDrawable[cursorIndex].getPadding(mTempRect);
4587        final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
4588        horizontal = Math.max(0.5f, horizontal - 0.5f);
4589        final int left = (int) (horizontal) - mTempRect.left;
4590        mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
4591                bottom + mTempRect.bottom);
4592    }
4593
4594    private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
4595        final boolean translate = cursorOffsetVertical != 0;
4596        if (translate) canvas.translate(0, cursorOffsetVertical);
4597        for (int i = 0; i < mCursorCount; i++) {
4598            mCursorDrawable[i].draw(canvas);
4599        }
4600        if (translate) canvas.translate(0, -cursorOffsetVertical);
4601    }
4602
4603    @Override
4604    public void getFocusedRect(Rect r) {
4605        if (mLayout == null) {
4606            super.getFocusedRect(r);
4607            return;
4608        }
4609
4610        int sel = getSelectionEnd();
4611        if (sel < 0) {
4612            super.getFocusedRect(r);
4613            return;
4614        }
4615
4616        int line = mLayout.getLineForOffset(sel);
4617        r.top = mLayout.getLineTop(line);
4618        r.bottom = mLayout.getLineBottom(line);
4619
4620        r.left = (int) mLayout.getPrimaryHorizontal(sel);
4621        r.right = r.left + 1;
4622
4623        // Adjust for padding and gravity.
4624        int paddingLeft = getCompoundPaddingLeft();
4625        int paddingTop = getExtendedPaddingTop();
4626        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4627            paddingTop += getVerticalOffset(false);
4628        }
4629        r.offset(paddingLeft, paddingTop);
4630    }
4631
4632    /**
4633     * Return the number of lines of text, or 0 if the internal Layout has not
4634     * been built.
4635     */
4636    public int getLineCount() {
4637        return mLayout != null ? mLayout.getLineCount() : 0;
4638    }
4639
4640    /**
4641     * Return the baseline for the specified line (0...getLineCount() - 1)
4642     * If bounds is not null, return the top, left, right, bottom extents
4643     * of the specified line in it. If the internal Layout has not been built,
4644     * return 0 and set bounds to (0, 0, 0, 0)
4645     * @param line which line to examine (0..getLineCount() - 1)
4646     * @param bounds Optional. If not null, it returns the extent of the line
4647     * @return the Y-coordinate of the baseline
4648     */
4649    public int getLineBounds(int line, Rect bounds) {
4650        if (mLayout == null) {
4651            if (bounds != null) {
4652                bounds.set(0, 0, 0, 0);
4653            }
4654            return 0;
4655        }
4656        else {
4657            int baseline = mLayout.getLineBounds(line, bounds);
4658
4659            int voffset = getExtendedPaddingTop();
4660            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4661                voffset += getVerticalOffset(true);
4662            }
4663            if (bounds != null) {
4664                bounds.offset(getCompoundPaddingLeft(), voffset);
4665            }
4666            return baseline + voffset;
4667        }
4668    }
4669
4670    @Override
4671    public int getBaseline() {
4672        if (mLayout == null) {
4673            return super.getBaseline();
4674        }
4675
4676        int voffset = 0;
4677        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4678            voffset = getVerticalOffset(true);
4679        }
4680
4681        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
4682    }
4683
4684    @Override
4685    public boolean onKeyDown(int keyCode, KeyEvent event) {
4686        int which = doKeyDown(keyCode, event, null);
4687        if (which == 0) {
4688            // Go through default dispatching.
4689            return super.onKeyDown(keyCode, event);
4690        }
4691
4692        return true;
4693    }
4694
4695    @Override
4696    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4697        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
4698
4699        int which = doKeyDown(keyCode, down, event);
4700        if (which == 0) {
4701            // Go through default dispatching.
4702            return super.onKeyMultiple(keyCode, repeatCount, event);
4703        }
4704        if (which == -1) {
4705            // Consumed the whole thing.
4706            return true;
4707        }
4708
4709        repeatCount--;
4710
4711        // We are going to dispatch the remaining events to either the input
4712        // or movement method.  To do this, we will just send a repeated stream
4713        // of down and up events until we have done the complete repeatCount.
4714        // It would be nice if those interfaces had an onKeyMultiple() method,
4715        // but adding that is a more complicated change.
4716        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
4717        if (which == 1) {
4718            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4719            while (--repeatCount > 0) {
4720                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
4721                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4722            }
4723            hideErrorIfUnchanged();
4724
4725        } else if (which == 2) {
4726            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4727            while (--repeatCount > 0) {
4728                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
4729                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4730            }
4731        }
4732
4733        return true;
4734    }
4735
4736    /**
4737     * Returns true if pressing ENTER in this field advances focus instead
4738     * of inserting the character.  This is true mostly in single-line fields,
4739     * but also in mail addresses and subjects which will display on multiple
4740     * lines but where it doesn't make sense to insert newlines.
4741     */
4742    private boolean shouldAdvanceFocusOnEnter() {
4743        if (mInput == null) {
4744            return false;
4745        }
4746
4747        if (mSingleLine) {
4748            return true;
4749        }
4750
4751        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4752            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4753            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
4754                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
4755                return true;
4756            }
4757        }
4758
4759        return false;
4760    }
4761
4762    /**
4763     * Returns true if pressing TAB in this field advances focus instead
4764     * of inserting the character.  Insert tabs only in multi-line editors.
4765     */
4766    private boolean shouldAdvanceFocusOnTab() {
4767        if (mInput != null && !mSingleLine) {
4768            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4769                int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4770                if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
4771                        || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
4772                    return false;
4773                }
4774            }
4775        }
4776        return true;
4777    }
4778
4779    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
4780        if (!isEnabled()) {
4781            return 0;
4782        }
4783
4784        switch (keyCode) {
4785            case KeyEvent.KEYCODE_ENTER:
4786                mEnterKeyIsDown = true;
4787                if (event.hasNoModifiers()) {
4788                    // When mInputContentType is set, we know that we are
4789                    // running in a "modern" cupcake environment, so don't need
4790                    // to worry about the application trying to capture
4791                    // enter key events.
4792                    if (mInputContentType != null) {
4793                        // If there is an action listener, given them a
4794                        // chance to consume the event.
4795                        if (mInputContentType.onEditorActionListener != null &&
4796                                mInputContentType.onEditorActionListener.onEditorAction(
4797                                this, EditorInfo.IME_NULL, event)) {
4798                            mInputContentType.enterDown = true;
4799                            // We are consuming the enter key for them.
4800                            return -1;
4801                        }
4802                    }
4803
4804                    // If our editor should move focus when enter is pressed, or
4805                    // this is a generated event from an IME action button, then
4806                    // don't let it be inserted into the text.
4807                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
4808                            || shouldAdvanceFocusOnEnter()) {
4809                        if (mOnClickListener != null) {
4810                            return 0;
4811                        }
4812                        return -1;
4813                    }
4814                }
4815                break;
4816
4817            case KeyEvent.KEYCODE_DPAD_CENTER:
4818                mDPadCenterIsDown = true;
4819                if (event.hasNoModifiers()) {
4820                    if (shouldAdvanceFocusOnEnter()) {
4821                        return 0;
4822                    }
4823                }
4824                break;
4825
4826            case KeyEvent.KEYCODE_TAB:
4827                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
4828                    if (shouldAdvanceFocusOnTab()) {
4829                        return 0;
4830                    }
4831                }
4832                break;
4833
4834                // Has to be done on key down (and not on key up) to correctly be intercepted.
4835            case KeyEvent.KEYCODE_BACK:
4836                if (mSelectionActionMode != null) {
4837                    stopSelectionActionMode();
4838                    return -1;
4839                }
4840                break;
4841        }
4842
4843        if (mInput != null) {
4844            resetErrorChangedFlag();
4845
4846            boolean doDown = true;
4847            if (otherEvent != null) {
4848                try {
4849                    beginBatchEdit();
4850                    final boolean handled = mInput.onKeyOther(this, (Editable) mText, otherEvent);
4851                    hideErrorIfUnchanged();
4852                    doDown = false;
4853                    if (handled) {
4854                        return -1;
4855                    }
4856                } catch (AbstractMethodError e) {
4857                    // onKeyOther was added after 1.0, so if it isn't
4858                    // implemented we need to try to dispatch as a regular down.
4859                } finally {
4860                    endBatchEdit();
4861                }
4862            }
4863
4864            if (doDown) {
4865                beginBatchEdit();
4866                final boolean handled = mInput.onKeyDown(this, (Editable) mText, keyCode, event);
4867                endBatchEdit();
4868                hideErrorIfUnchanged();
4869                if (handled) return 1;
4870            }
4871        }
4872
4873        // bug 650865: sometimes we get a key event before a layout.
4874        // don't try to move around if we don't know the layout.
4875
4876        if (mMovement != null && mLayout != null) {
4877            boolean doDown = true;
4878            if (otherEvent != null) {
4879                try {
4880                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
4881                            otherEvent);
4882                    doDown = false;
4883                    if (handled) {
4884                        return -1;
4885                    }
4886                } catch (AbstractMethodError e) {
4887                    // onKeyOther was added after 1.0, so if it isn't
4888                    // implemented we need to try to dispatch as a regular down.
4889                }
4890            }
4891            if (doDown) {
4892                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
4893                    return 2;
4894            }
4895        }
4896
4897        return 0;
4898    }
4899
4900    /**
4901     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
4902     * can be recorded.
4903     * @hide
4904     */
4905    public void resetErrorChangedFlag() {
4906        /*
4907         * Keep track of what the error was before doing the input
4908         * so that if an input filter changed the error, we leave
4909         * that error showing.  Otherwise, we take down whatever
4910         * error was showing when the user types something.
4911         */
4912        mErrorWasChanged = false;
4913    }
4914
4915    /**
4916     * @hide
4917     */
4918    public void hideErrorIfUnchanged() {
4919        if (mError != null && !mErrorWasChanged) {
4920            setError(null, null);
4921        }
4922    }
4923
4924    @Override
4925    public boolean onKeyUp(int keyCode, KeyEvent event) {
4926        if (!isEnabled()) {
4927            return super.onKeyUp(keyCode, event);
4928        }
4929
4930        switch (keyCode) {
4931            case KeyEvent.KEYCODE_DPAD_CENTER:
4932                mDPadCenterIsDown = false;
4933                if (event.hasNoModifiers()) {
4934                    /*
4935                     * If there is a click listener, just call through to
4936                     * super, which will invoke it.
4937                     *
4938                     * If there isn't a click listener, try to show the soft
4939                     * input method.  (It will also
4940                     * call performClick(), but that won't do anything in
4941                     * this case.)
4942                     */
4943                    if (mOnClickListener == null) {
4944                        if (mMovement != null && mText instanceof Editable
4945                                && mLayout != null && onCheckIsTextEditor()) {
4946                            InputMethodManager imm = InputMethodManager.peekInstance();
4947                            if (imm != null) imm.showSoftInput(this, 0);
4948                        }
4949                    }
4950                }
4951                return super.onKeyUp(keyCode, event);
4952
4953            case KeyEvent.KEYCODE_ENTER:
4954                mEnterKeyIsDown = false;
4955                if (event.hasNoModifiers()) {
4956                    if (mInputContentType != null
4957                            && mInputContentType.onEditorActionListener != null
4958                            && mInputContentType.enterDown) {
4959                        mInputContentType.enterDown = false;
4960                        if (mInputContentType.onEditorActionListener.onEditorAction(
4961                                this, EditorInfo.IME_NULL, event)) {
4962                            return true;
4963                        }
4964                    }
4965
4966                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
4967                            || shouldAdvanceFocusOnEnter()) {
4968                        /*
4969                         * If there is a click listener, just call through to
4970                         * super, which will invoke it.
4971                         *
4972                         * If there isn't a click listener, try to advance focus,
4973                         * but still call through to super, which will reset the
4974                         * pressed state and longpress state.  (It will also
4975                         * call performClick(), but that won't do anything in
4976                         * this case.)
4977                         */
4978                        if (mOnClickListener == null) {
4979                            View v = focusSearch(FOCUS_DOWN);
4980
4981                            if (v != null) {
4982                                if (!v.requestFocus(FOCUS_DOWN)) {
4983                                    throw new IllegalStateException(
4984                                            "focus search returned a view " +
4985                                            "that wasn't able to take focus!");
4986                                }
4987
4988                                /*
4989                                 * Return true because we handled the key; super
4990                                 * will return false because there was no click
4991                                 * listener.
4992                                 */
4993                                super.onKeyUp(keyCode, event);
4994                                return true;
4995                            } else if ((event.getFlags()
4996                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4997                                // No target for next focus, but make sure the IME
4998                                // if this came from it.
4999                                InputMethodManager imm = InputMethodManager.peekInstance();
5000                                if (imm != null && imm.isActive(this)) {
5001                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5002                                }
5003                            }
5004                        }
5005                    }
5006                    return super.onKeyUp(keyCode, event);
5007                }
5008                break;
5009        }
5010
5011        if (mInput != null)
5012            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
5013                return true;
5014
5015        if (mMovement != null && mLayout != null)
5016            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5017                return true;
5018
5019        return super.onKeyUp(keyCode, event);
5020    }
5021
5022    @Override public boolean onCheckIsTextEditor() {
5023        return mInputType != EditorInfo.TYPE_NULL;
5024    }
5025
5026    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5027        if (onCheckIsTextEditor() && isEnabled()) {
5028            if (mInputMethodState == null) {
5029                mInputMethodState = new InputMethodState();
5030            }
5031            outAttrs.inputType = mInputType;
5032            if (mInputContentType != null) {
5033                outAttrs.imeOptions = mInputContentType.imeOptions;
5034                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
5035                outAttrs.actionLabel = mInputContentType.imeActionLabel;
5036                outAttrs.actionId = mInputContentType.imeActionId;
5037                outAttrs.extras = mInputContentType.extras;
5038            } else {
5039                outAttrs.imeOptions = EditorInfo.IME_NULL;
5040            }
5041            if (focusSearch(FOCUS_DOWN) != null) {
5042                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5043            }
5044            if (focusSearch(FOCUS_UP) != null) {
5045                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5046            }
5047            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5048                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
5049                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
5050                    // An action has not been set, but the enter key will move to
5051                    // the next focus, so set the action to that.
5052                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
5053                } else {
5054                    // An action has not been set, and there is no focus to move
5055                    // to, so let's just supply a "done" action.
5056                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
5057                }
5058                if (!shouldAdvanceFocusOnEnter()) {
5059                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5060                }
5061            }
5062            if (isMultilineInputType(outAttrs.inputType)) {
5063                // Multi-line text editors should always show an enter key.
5064                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5065            }
5066            outAttrs.hintText = mHint;
5067            if (mText instanceof Editable) {
5068                InputConnection ic = new EditableInputConnection(this);
5069                outAttrs.initialSelStart = getSelectionStart();
5070                outAttrs.initialSelEnd = getSelectionEnd();
5071                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
5072                return ic;
5073            }
5074        }
5075        return null;
5076    }
5077
5078    /**
5079     * If this TextView contains editable content, extract a portion of it
5080     * based on the information in <var>request</var> in to <var>outText</var>.
5081     * @return Returns true if the text was successfully extracted, else false.
5082     */
5083    public boolean extractText(ExtractedTextRequest request,
5084            ExtractedText outText) {
5085        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
5086                EXTRACT_UNKNOWN, outText);
5087    }
5088
5089    static final int EXTRACT_NOTHING = -2;
5090    static final int EXTRACT_UNKNOWN = -1;
5091
5092    boolean extractTextInternal(ExtractedTextRequest request,
5093            int partialStartOffset, int partialEndOffset, int delta,
5094            ExtractedText outText) {
5095        final CharSequence content = mText;
5096        if (content != null) {
5097            if (partialStartOffset != EXTRACT_NOTHING) {
5098                final int N = content.length();
5099                if (partialStartOffset < 0) {
5100                    outText.partialStartOffset = outText.partialEndOffset = -1;
5101                    partialStartOffset = 0;
5102                    partialEndOffset = N;
5103                } else {
5104                    // Now use the delta to determine the actual amount of text
5105                    // we need.
5106                    partialEndOffset += delta;
5107                    // Adjust offsets to ensure we contain full spans.
5108                    if (content instanceof Spanned) {
5109                        Spanned spanned = (Spanned)content;
5110                        Object[] spans = spanned.getSpans(partialStartOffset,
5111                                partialEndOffset, ParcelableSpan.class);
5112                        int i = spans.length;
5113                        while (i > 0) {
5114                            i--;
5115                            int j = spanned.getSpanStart(spans[i]);
5116                            if (j < partialStartOffset) partialStartOffset = j;
5117                            j = spanned.getSpanEnd(spans[i]);
5118                            if (j > partialEndOffset) partialEndOffset = j;
5119                        }
5120                    }
5121                    outText.partialStartOffset = partialStartOffset;
5122                    outText.partialEndOffset = partialEndOffset - delta;
5123
5124                    if (partialStartOffset > N) {
5125                        partialStartOffset = N;
5126                    } else if (partialStartOffset < 0) {
5127                        partialStartOffset = 0;
5128                    }
5129                    if (partialEndOffset > N) {
5130                        partialEndOffset = N;
5131                    } else if (partialEndOffset < 0) {
5132                        partialEndOffset = 0;
5133                    }
5134                }
5135                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
5136                    outText.text = content.subSequence(partialStartOffset,
5137                            partialEndOffset);
5138                } else {
5139                    outText.text = TextUtils.substring(content, partialStartOffset,
5140                            partialEndOffset);
5141                }
5142            } else {
5143                outText.partialStartOffset = 0;
5144                outText.partialEndOffset = 0;
5145                outText.text = "";
5146            }
5147            outText.flags = 0;
5148            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
5149                outText.flags |= ExtractedText.FLAG_SELECTING;
5150            }
5151            if (mSingleLine) {
5152                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
5153            }
5154            outText.startOffset = 0;
5155            outText.selectionStart = getSelectionStart();
5156            outText.selectionEnd = getSelectionEnd();
5157            return true;
5158        }
5159        return false;
5160    }
5161
5162    boolean reportExtractedText() {
5163        final InputMethodState ims = mInputMethodState;
5164        if (ims != null) {
5165            final boolean contentChanged = ims.mContentChanged;
5166            if (contentChanged || ims.mSelectionModeChanged) {
5167                ims.mContentChanged = false;
5168                ims.mSelectionModeChanged = false;
5169                final ExtractedTextRequest req = mInputMethodState.mExtracting;
5170                if (req != null) {
5171                    InputMethodManager imm = InputMethodManager.peekInstance();
5172                    if (imm != null) {
5173                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
5174                                + ims.mChangedStart + " end=" + ims.mChangedEnd
5175                                + " delta=" + ims.mChangedDelta);
5176                        if (ims.mChangedStart < 0 && !contentChanged) {
5177                            ims.mChangedStart = EXTRACT_NOTHING;
5178                        }
5179                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
5180                                ims.mChangedDelta, ims.mTmpExtracted)) {
5181                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
5182                                    + ims.mTmpExtracted.partialStartOffset
5183                                    + " end=" + ims.mTmpExtracted.partialEndOffset
5184                                    + ": " + ims.mTmpExtracted.text);
5185                            imm.updateExtractedText(this, req.token,
5186                                    mInputMethodState.mTmpExtracted);
5187                            ims.mChangedStart = EXTRACT_UNKNOWN;
5188                            ims.mChangedEnd = EXTRACT_UNKNOWN;
5189                            ims.mChangedDelta = 0;
5190                            ims.mContentChanged = false;
5191                            return true;
5192                        }
5193                    }
5194                }
5195            }
5196        }
5197        return false;
5198    }
5199
5200    /**
5201     * This is used to remove all style-impacting spans from text before new
5202     * extracted text is being replaced into it, so that we don't have any
5203     * lingering spans applied during the replace.
5204     */
5205    static void removeParcelableSpans(Spannable spannable, int start, int end) {
5206        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5207        int i = spans.length;
5208        while (i > 0) {
5209            i--;
5210            spannable.removeSpan(spans[i]);
5211        }
5212    }
5213
5214    /**
5215     * Apply to this text view the given extracted text, as previously
5216     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5217     */
5218    public void setExtractedText(ExtractedText text) {
5219        Editable content = getEditableText();
5220        if (text.text != null) {
5221            if (content == null) {
5222                setText(text.text, TextView.BufferType.EDITABLE);
5223            } else if (text.partialStartOffset < 0) {
5224                removeParcelableSpans(content, 0, content.length());
5225                content.replace(0, content.length(), text.text);
5226            } else {
5227                final int N = content.length();
5228                int start = text.partialStartOffset;
5229                if (start > N) start = N;
5230                int end = text.partialEndOffset;
5231                if (end > N) end = N;
5232                removeParcelableSpans(content, start, end);
5233                content.replace(start, end, text.text);
5234            }
5235        }
5236
5237        // Now set the selection position...  make sure it is in range, to
5238        // avoid crashes.  If this is a partial update, it is possible that
5239        // the underlying text may have changed, causing us problems here.
5240        // Also we just don't want to trust clients to do the right thing.
5241        Spannable sp = (Spannable)getText();
5242        final int N = sp.length();
5243        int start = text.selectionStart;
5244        if (start < 0) start = 0;
5245        else if (start > N) start = N;
5246        int end = text.selectionEnd;
5247        if (end < 0) end = 0;
5248        else if (end > N) end = N;
5249        Selection.setSelection(sp, start, end);
5250
5251        // Finally, update the selection mode.
5252        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5253            MetaKeyKeyListener.startSelecting(this, sp);
5254        } else {
5255            MetaKeyKeyListener.stopSelecting(this, sp);
5256        }
5257    }
5258
5259    /**
5260     * @hide
5261     */
5262    public void setExtracting(ExtractedTextRequest req) {
5263        if (mInputMethodState != null) {
5264            mInputMethodState.mExtracting = req;
5265        }
5266        // This stops a possible text selection mode. Maybe not intended.
5267        hideControllers();
5268    }
5269
5270    /**
5271     * Called by the framework in response to a text completion from
5272     * the current input method, provided by it calling
5273     * {@link InputConnection#commitCompletion
5274     * InputConnection.commitCompletion()}.  The default implementation does
5275     * nothing; text views that are supporting auto-completion should override
5276     * this to do their desired behavior.
5277     *
5278     * @param text The auto complete text the user has selected.
5279     */
5280    public void onCommitCompletion(CompletionInfo text) {
5281        // intentionally empty
5282    }
5283
5284    /**
5285     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5286     * a dictionnary) from the current input method, provided by it calling
5287     * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5288     * implementation flashes the background of the corrected word to provide feedback to the user.
5289     *
5290     * @param info The auto correct info about the text that was corrected.
5291     */
5292    public void onCommitCorrection(CorrectionInfo info) {
5293        if (mCorrectionHighlighter == null) {
5294            mCorrectionHighlighter = new CorrectionHighlighter();
5295        } else {
5296            mCorrectionHighlighter.invalidate(false);
5297        }
5298
5299        mCorrectionHighlighter.highlight(info);
5300    }
5301
5302    private class CorrectionHighlighter {
5303        private final Path mPath = new Path();
5304        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
5305        private int mStart, mEnd;
5306        private long mFadingStartTime;
5307        private final static int FADE_OUT_DURATION = 400;
5308
5309        public CorrectionHighlighter() {
5310            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
5311            mPaint.setStyle(Paint.Style.FILL);
5312        }
5313
5314        public void highlight(CorrectionInfo info) {
5315            mStart = info.getOffset();
5316            mEnd = mStart + info.getNewText().length();
5317            mFadingStartTime = SystemClock.uptimeMillis();
5318
5319            if (mStart < 0 || mEnd < 0) {
5320                stopAnimation();
5321            }
5322        }
5323
5324        public void draw(Canvas canvas, int cursorOffsetVertical) {
5325            if (updatePath() && updatePaint()) {
5326                if (cursorOffsetVertical != 0) {
5327                    canvas.translate(0, cursorOffsetVertical);
5328                }
5329
5330                canvas.drawPath(mPath, mPaint);
5331
5332                if (cursorOffsetVertical != 0) {
5333                    canvas.translate(0, -cursorOffsetVertical);
5334                }
5335                invalidate(true);
5336            } else {
5337                stopAnimation();
5338                invalidate(false);
5339            }
5340        }
5341
5342        private boolean updatePaint() {
5343            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
5344            if (duration > FADE_OUT_DURATION) return false;
5345
5346            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
5347            final int highlightColorAlpha = Color.alpha(mHighlightColor);
5348            final int color = (mHighlightColor & 0x00FFFFFF) +
5349                    ((int) (highlightColorAlpha * coef) << 24);
5350            mPaint.setColor(color);
5351            return true;
5352        }
5353
5354        private boolean updatePath() {
5355            final Layout layout = TextView.this.mLayout;
5356            if (layout == null) return false;
5357
5358            // Update in case text is edited while the animation is run
5359            final int length = mText.length();
5360            int start = Math.min(length, mStart);
5361            int end = Math.min(length, mEnd);
5362
5363            mPath.reset();
5364            TextView.this.mLayout.getSelectionPath(start, end, mPath);
5365            return true;
5366        }
5367
5368        private void invalidate(boolean delayed) {
5369            if (TextView.this.mLayout == null) return;
5370
5371            synchronized (sTempRect) {
5372                mPath.computeBounds(sTempRect, false);
5373
5374                int left = getCompoundPaddingLeft();
5375                int top = getExtendedPaddingTop() + getVerticalOffset(true);
5376
5377                if (delayed) {
5378                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
5379                            left + (int) sTempRect.left, top + (int) sTempRect.top,
5380                            left + (int) sTempRect.right, top + (int) sTempRect.bottom);
5381                } else {
5382                    TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top,
5383                            (int) sTempRect.right, (int) sTempRect.bottom);
5384                }
5385            }
5386        }
5387
5388        private void stopAnimation() {
5389            TextView.this.mCorrectionHighlighter = null;
5390        }
5391    }
5392
5393    public void beginBatchEdit() {
5394        mInBatchEditControllers = true;
5395        final InputMethodState ims = mInputMethodState;
5396        if (ims != null) {
5397            int nesting = ++ims.mBatchEditNesting;
5398            if (nesting == 1) {
5399                ims.mCursorChanged = false;
5400                ims.mChangedDelta = 0;
5401                if (ims.mContentChanged) {
5402                    // We already have a pending change from somewhere else,
5403                    // so turn this into a full update.
5404                    ims.mChangedStart = 0;
5405                    ims.mChangedEnd = mText.length();
5406                } else {
5407                    ims.mChangedStart = EXTRACT_UNKNOWN;
5408                    ims.mChangedEnd = EXTRACT_UNKNOWN;
5409                    ims.mContentChanged = false;
5410                }
5411                onBeginBatchEdit();
5412            }
5413        }
5414    }
5415
5416    public void endBatchEdit() {
5417        mInBatchEditControllers = false;
5418        final InputMethodState ims = mInputMethodState;
5419        if (ims != null) {
5420            int nesting = --ims.mBatchEditNesting;
5421            if (nesting == 0) {
5422                finishBatchEdit(ims);
5423            }
5424        }
5425    }
5426
5427    void ensureEndedBatchEdit() {
5428        final InputMethodState ims = mInputMethodState;
5429        if (ims != null && ims.mBatchEditNesting != 0) {
5430            ims.mBatchEditNesting = 0;
5431            finishBatchEdit(ims);
5432        }
5433    }
5434
5435    void finishBatchEdit(final InputMethodState ims) {
5436        onEndBatchEdit();
5437
5438        if (ims.mContentChanged || ims.mSelectionModeChanged) {
5439            updateAfterEdit();
5440            reportExtractedText();
5441        } else if (ims.mCursorChanged) {
5442            // Cheezy way to get us to report the current cursor location.
5443            invalidateCursor();
5444        }
5445    }
5446
5447    void updateAfterEdit() {
5448        invalidate();
5449        int curs = getSelectionStart();
5450
5451        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5452            registerForPreDraw();
5453        }
5454
5455        if (curs >= 0) {
5456            mHighlightPathBogus = true;
5457            makeBlink();
5458        }
5459
5460        checkForResize();
5461    }
5462
5463    /**
5464     * Called by the framework in response to a request to begin a batch
5465     * of edit operations through a call to link {@link #beginBatchEdit()}.
5466     */
5467    public void onBeginBatchEdit() {
5468        // intentionally empty
5469    }
5470
5471    /**
5472     * Called by the framework in response to a request to end a batch
5473     * of edit operations through a call to link {@link #endBatchEdit}.
5474     */
5475    public void onEndBatchEdit() {
5476        // intentionally empty
5477    }
5478
5479    /**
5480     * Called by the framework in response to a private command from the
5481     * current method, provided by it calling
5482     * {@link InputConnection#performPrivateCommand
5483     * InputConnection.performPrivateCommand()}.
5484     *
5485     * @param action The action name of the command.
5486     * @param data Any additional data for the command.  This may be null.
5487     * @return Return true if you handled the command, else false.
5488     */
5489    public boolean onPrivateIMECommand(String action, Bundle data) {
5490        return false;
5491    }
5492
5493    private void nullLayouts() {
5494        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5495            mSavedLayout = (BoringLayout) mLayout;
5496        }
5497        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5498            mSavedHintLayout = (BoringLayout) mHintLayout;
5499        }
5500
5501        mLayout = mHintLayout = null;
5502
5503        // Since it depends on the value of mLayout
5504        prepareCursorControllers();
5505    }
5506
5507    /**
5508     * Make a new Layout based on the already-measured size of the view,
5509     * on the assumption that it was measured correctly at some point.
5510     */
5511    private void assumeLayout() {
5512        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5513
5514        if (width < 1) {
5515            width = 0;
5516        }
5517
5518        int physicalWidth = width;
5519
5520        if (mHorizontallyScrolling) {
5521            width = VERY_WIDE;
5522        }
5523
5524        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5525                      physicalWidth, false);
5526    }
5527
5528    /**
5529     * The width passed in is now the desired layout width,
5530     * not the full view width with padding.
5531     * {@hide}
5532     */
5533    protected void makeNewLayout(int w, int hintWidth,
5534                                 BoringLayout.Metrics boring,
5535                                 BoringLayout.Metrics hintBoring,
5536                                 int ellipsisWidth, boolean bringIntoView) {
5537        stopMarquee();
5538
5539        mHighlightPathBogus = true;
5540
5541        if (w < 0) {
5542            w = 0;
5543        }
5544        if (hintWidth < 0) {
5545            hintWidth = 0;
5546        }
5547
5548        Layout.Alignment alignment;
5549        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
5550        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
5551            case Gravity.CENTER_HORIZONTAL:
5552                alignment = Layout.Alignment.ALIGN_CENTER;
5553                break;
5554
5555            case Gravity.RIGHT:
5556                // Note, Layout resolves ALIGN_OPPOSITE to left or
5557                // right based on the paragraph direction.
5558                alignment = Layout.Alignment.ALIGN_OPPOSITE;
5559                break;
5560
5561            default:
5562                alignment = Layout.Alignment.ALIGN_NORMAL;
5563        }
5564
5565        boolean shouldEllipsize = mEllipsize != null && mInput == null;
5566
5567        if (mText instanceof Spannable) {
5568            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
5569                    alignment, mSpacingMult,
5570                    mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
5571                    ellipsisWidth);
5572        } else {
5573            if (boring == UNKNOWN_BORING) {
5574                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
5575                if (boring != null) {
5576                    mBoring = boring;
5577                }
5578            }
5579
5580            if (boring != null) {
5581                if (boring.width <= w &&
5582                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
5583                    if (mSavedLayout != null) {
5584                        mLayout = mSavedLayout.
5585                                replaceOrMake(mTransformed, mTextPaint,
5586                                w, alignment, mSpacingMult, mSpacingAdd,
5587                                boring, mIncludePad);
5588                    } else {
5589                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
5590                                w, alignment, mSpacingMult, mSpacingAdd,
5591                                boring, mIncludePad);
5592                    }
5593
5594                    mSavedLayout = (BoringLayout) mLayout;
5595                } else if (shouldEllipsize && boring.width <= w) {
5596                    if (mSavedLayout != null) {
5597                        mLayout = mSavedLayout.
5598                                replaceOrMake(mTransformed, mTextPaint,
5599                                w, alignment, mSpacingMult, mSpacingAdd,
5600                                boring, mIncludePad, mEllipsize,
5601                                ellipsisWidth);
5602                    } else {
5603                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
5604                                w, alignment, mSpacingMult, mSpacingAdd,
5605                                boring, mIncludePad, mEllipsize,
5606                                ellipsisWidth);
5607                    }
5608                } else if (shouldEllipsize) {
5609                    mLayout = new StaticLayout(mTransformed,
5610                                0, mTransformed.length(),
5611                                mTextPaint, w, alignment, mSpacingMult,
5612                                mSpacingAdd, mIncludePad, mEllipsize,
5613                                ellipsisWidth);
5614                } else {
5615                    mLayout = new StaticLayout(mTransformed, mTextPaint,
5616                            w, alignment, mSpacingMult, mSpacingAdd,
5617                            mIncludePad);
5618                }
5619            } else if (shouldEllipsize) {
5620                mLayout = new StaticLayout(mTransformed,
5621                            0, mTransformed.length(),
5622                            mTextPaint, w, alignment, mSpacingMult,
5623                            mSpacingAdd, mIncludePad, mEllipsize,
5624                            ellipsisWidth);
5625            } else {
5626                mLayout = new StaticLayout(mTransformed, mTextPaint,
5627                        w, alignment, mSpacingMult, mSpacingAdd,
5628                        mIncludePad);
5629            }
5630        }
5631
5632        shouldEllipsize = mEllipsize != null;
5633        mHintLayout = null;
5634
5635        if (mHint != null) {
5636            if (shouldEllipsize) hintWidth = w;
5637
5638            if (hintBoring == UNKNOWN_BORING) {
5639                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
5640                                                   mHintBoring);
5641                if (hintBoring != null) {
5642                    mHintBoring = hintBoring;
5643                }
5644            }
5645
5646            if (hintBoring != null) {
5647                if (hintBoring.width <= hintWidth &&
5648                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
5649                    if (mSavedHintLayout != null) {
5650                        mHintLayout = mSavedHintLayout.
5651                                replaceOrMake(mHint, mTextPaint,
5652                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5653                                hintBoring, mIncludePad);
5654                    } else {
5655                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
5656                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5657                                hintBoring, mIncludePad);
5658                    }
5659
5660                    mSavedHintLayout = (BoringLayout) mHintLayout;
5661                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5662                    if (mSavedHintLayout != null) {
5663                        mHintLayout = mSavedHintLayout.
5664                                replaceOrMake(mHint, mTextPaint,
5665                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5666                                hintBoring, mIncludePad, mEllipsize,
5667                                ellipsisWidth);
5668                    } else {
5669                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
5670                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5671                                hintBoring, mIncludePad, mEllipsize,
5672                                ellipsisWidth);
5673                    }
5674                } else if (shouldEllipsize) {
5675                    mHintLayout = new StaticLayout(mHint,
5676                                0, mHint.length(),
5677                                mTextPaint, hintWidth, alignment, mSpacingMult,
5678                                mSpacingAdd, mIncludePad, mEllipsize,
5679                                ellipsisWidth);
5680                } else {
5681                    mHintLayout = new StaticLayout(mHint, mTextPaint,
5682                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
5683                            mIncludePad);
5684                }
5685            } else if (shouldEllipsize) {
5686                mHintLayout = new StaticLayout(mHint,
5687                            0, mHint.length(),
5688                            mTextPaint, hintWidth, alignment, mSpacingMult,
5689                            mSpacingAdd, mIncludePad, mEllipsize,
5690                            ellipsisWidth);
5691            } else {
5692                mHintLayout = new StaticLayout(mHint, mTextPaint,
5693                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
5694                        mIncludePad);
5695            }
5696        }
5697
5698        if (bringIntoView) {
5699            registerForPreDraw();
5700        }
5701
5702        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5703            if (!compressText(ellipsisWidth)) {
5704                final int height = mLayoutParams.height;
5705                // If the size of the view does not depend on the size of the text, try to
5706                // start the marquee immediately
5707                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
5708                    startMarquee();
5709                } else {
5710                    // Defer the start of the marquee until we know our width (see setFrame())
5711                    mRestartMarquee = true;
5712                }
5713            }
5714        }
5715
5716        // CursorControllers need a non-null mLayout
5717        prepareCursorControllers();
5718    }
5719
5720    private boolean compressText(float width) {
5721        if (isHardwareAccelerated()) return false;
5722
5723        // Only compress the text if it hasn't been compressed by the previous pass
5724        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
5725                mTextPaint.getTextScaleX() == 1.0f) {
5726            final float textWidth = mLayout.getLineWidth(0);
5727            final float overflow = (textWidth + 1.0f - width) / width;
5728            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
5729                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
5730                post(new Runnable() {
5731                    public void run() {
5732                        requestLayout();
5733                    }
5734                });
5735                return true;
5736            }
5737        }
5738
5739        return false;
5740    }
5741
5742    private static int desired(Layout layout) {
5743        int n = layout.getLineCount();
5744        CharSequence text = layout.getText();
5745        float max = 0;
5746
5747        // if any line was wrapped, we can't use it.
5748        // but it's ok for the last line not to have a newline
5749
5750        for (int i = 0; i < n - 1; i++) {
5751            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
5752                return -1;
5753        }
5754
5755        for (int i = 0; i < n; i++) {
5756            max = Math.max(max, layout.getLineWidth(i));
5757        }
5758
5759        return (int) FloatMath.ceil(max);
5760    }
5761
5762    /**
5763     * Set whether the TextView includes extra top and bottom padding to make
5764     * room for accents that go above the normal ascent and descent.
5765     * The default is true.
5766     *
5767     * @attr ref android.R.styleable#TextView_includeFontPadding
5768     */
5769    public void setIncludeFontPadding(boolean includepad) {
5770        mIncludePad = includepad;
5771
5772        if (mLayout != null) {
5773            nullLayouts();
5774            requestLayout();
5775            invalidate();
5776        }
5777    }
5778
5779    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
5780
5781    @Override
5782    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5783        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
5784        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
5785        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
5786        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
5787
5788        int width;
5789        int height;
5790
5791        BoringLayout.Metrics boring = UNKNOWN_BORING;
5792        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
5793
5794        int des = -1;
5795        boolean fromexisting = false;
5796
5797        if (widthMode == MeasureSpec.EXACTLY) {
5798            // Parent has told us how big to be. So be it.
5799            width = widthSize;
5800        } else {
5801            if (mLayout != null && mEllipsize == null) {
5802                des = desired(mLayout);
5803            }
5804
5805            if (des < 0) {
5806                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
5807                if (boring != null) {
5808                    mBoring = boring;
5809                }
5810            } else {
5811                fromexisting = true;
5812            }
5813
5814            if (boring == null || boring == UNKNOWN_BORING) {
5815                if (des < 0) {
5816                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
5817                }
5818
5819                width = des;
5820            } else {
5821                width = boring.width;
5822            }
5823
5824            final Drawables dr = mDrawables;
5825            if (dr != null) {
5826                width = Math.max(width, dr.mDrawableWidthTop);
5827                width = Math.max(width, dr.mDrawableWidthBottom);
5828            }
5829
5830            if (mHint != null) {
5831                int hintDes = -1;
5832                int hintWidth;
5833
5834                if (mHintLayout != null && mEllipsize == null) {
5835                    hintDes = desired(mHintLayout);
5836                }
5837
5838                if (hintDes < 0) {
5839                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
5840                    if (hintBoring != null) {
5841                        mHintBoring = hintBoring;
5842                    }
5843                }
5844
5845                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
5846                    if (hintDes < 0) {
5847                        hintDes = (int) FloatMath.ceil(
5848                                Layout.getDesiredWidth(mHint, mTextPaint));
5849                    }
5850
5851                    hintWidth = hintDes;
5852                } else {
5853                    hintWidth = hintBoring.width;
5854                }
5855
5856                if (hintWidth > width) {
5857                    width = hintWidth;
5858                }
5859            }
5860
5861            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
5862
5863            if (mMaxWidthMode == EMS) {
5864                width = Math.min(width, mMaxWidth * getLineHeight());
5865            } else {
5866                width = Math.min(width, mMaxWidth);
5867            }
5868
5869            if (mMinWidthMode == EMS) {
5870                width = Math.max(width, mMinWidth * getLineHeight());
5871            } else {
5872                width = Math.max(width, mMinWidth);
5873            }
5874
5875            // Check against our minimum width
5876            width = Math.max(width, getSuggestedMinimumWidth());
5877
5878            if (widthMode == MeasureSpec.AT_MOST) {
5879                width = Math.min(widthSize, width);
5880            }
5881        }
5882
5883        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5884        int unpaddedWidth = want;
5885
5886        if (mHorizontallyScrolling) want = VERY_WIDE;
5887
5888        int hintWant = want;
5889        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5890
5891        if (mLayout == null) {
5892            makeNewLayout(want, hintWant, boring, hintBoring,
5893                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5894        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5895                   (mLayout.getEllipsizedWidth() !=
5896                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5897            if (mHint == null && mEllipsize == null &&
5898                    want > mLayout.getWidth() &&
5899                    (mLayout instanceof BoringLayout ||
5900                            (fromexisting && des >= 0 && des <= want))) {
5901                mLayout.increaseWidthTo(want);
5902            } else {
5903                makeNewLayout(want, hintWant, boring, hintBoring,
5904                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5905            }
5906        } else {
5907            // Width has not changed.
5908        }
5909
5910        if (heightMode == MeasureSpec.EXACTLY) {
5911            // Parent has told us how big to be. So be it.
5912            height = heightSize;
5913            mDesiredHeightAtMeasure = -1;
5914        } else {
5915            int desired = getDesiredHeight();
5916
5917            height = desired;
5918            mDesiredHeightAtMeasure = desired;
5919
5920            if (heightMode == MeasureSpec.AT_MOST) {
5921                height = Math.min(desired, heightSize);
5922            }
5923        }
5924
5925        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
5926        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
5927            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
5928        }
5929
5930        /*
5931         * We didn't let makeNewLayout() register to bring the cursor into view,
5932         * so do it here if there is any possibility that it is needed.
5933         */
5934        if (mMovement != null ||
5935            mLayout.getWidth() > unpaddedWidth ||
5936            mLayout.getHeight() > unpaddedHeight) {
5937            registerForPreDraw();
5938        } else {
5939            scrollTo(0, 0);
5940        }
5941
5942        setMeasuredDimension(width, height);
5943    }
5944
5945    private int getDesiredHeight() {
5946        return Math.max(
5947                getDesiredHeight(mLayout, true),
5948                getDesiredHeight(mHintLayout, mEllipsize != null));
5949    }
5950
5951    private int getDesiredHeight(Layout layout, boolean cap) {
5952        if (layout == null) {
5953            return 0;
5954        }
5955
5956        int linecount = layout.getLineCount();
5957        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5958        int desired = layout.getLineTop(linecount);
5959
5960        final Drawables dr = mDrawables;
5961        if (dr != null) {
5962            desired = Math.max(desired, dr.mDrawableHeightLeft);
5963            desired = Math.max(desired, dr.mDrawableHeightRight);
5964        }
5965
5966        desired += pad;
5967        layout.setMaximumVisibleLineCount(0);
5968
5969        if (mMaxMode == LINES) {
5970            /*
5971             * Don't cap the hint to a certain number of lines.
5972             * (Do cap it, though, if we have a maximum pixel height.)
5973             */
5974            if (cap) {
5975                if (linecount > mMaximum) {
5976                    layout.setMaximumVisibleLineCount(mMaximum);
5977                    desired = layout.getLineTop(mMaximum);
5978
5979                    if (dr != null) {
5980                        desired = Math.max(desired, dr.mDrawableHeightLeft);
5981                        desired = Math.max(desired, dr.mDrawableHeightRight);
5982                    }
5983
5984                    desired += pad;
5985                    linecount = mMaximum;
5986                }
5987            }
5988        } else {
5989            desired = Math.min(desired, mMaximum);
5990        }
5991
5992        if (mMinMode == LINES) {
5993            if (linecount < mMinimum) {
5994                desired += getLineHeight() * (mMinimum - linecount);
5995            }
5996        } else {
5997            desired = Math.max(desired, mMinimum);
5998        }
5999
6000        // Check against our minimum height
6001        desired = Math.max(desired, getSuggestedMinimumHeight());
6002
6003        return desired;
6004    }
6005
6006    /**
6007     * Check whether a change to the existing text layout requires a
6008     * new view layout.
6009     */
6010    private void checkForResize() {
6011        boolean sizeChanged = false;
6012
6013        if (mLayout != null) {
6014            // Check if our width changed
6015            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6016                sizeChanged = true;
6017                invalidate();
6018            }
6019
6020            // Check if our height changed
6021            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6022                int desiredHeight = getDesiredHeight();
6023
6024                if (desiredHeight != this.getHeight()) {
6025                    sizeChanged = true;
6026                }
6027            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6028                if (mDesiredHeightAtMeasure >= 0) {
6029                    int desiredHeight = getDesiredHeight();
6030
6031                    if (desiredHeight != mDesiredHeightAtMeasure) {
6032                        sizeChanged = true;
6033                    }
6034                }
6035            }
6036        }
6037
6038        if (sizeChanged) {
6039            requestLayout();
6040            // caller will have already invalidated
6041        }
6042    }
6043
6044    /**
6045     * Check whether entirely new text requires a new view layout
6046     * or merely a new text layout.
6047     */
6048    private void checkForRelayout() {
6049        // If we have a fixed width, we can just swap in a new text layout
6050        // if the text height stays the same or if the view height is fixed.
6051
6052        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6053                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6054                (mHint == null || mHintLayout != null) &&
6055                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6056            // Static width, so try making a new text layout.
6057
6058            int oldht = mLayout.getHeight();
6059            int want = mLayout.getWidth();
6060            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6061
6062            /*
6063             * No need to bring the text into view, since the size is not
6064             * changing (unless we do the requestLayout(), in which case it
6065             * will happen at measure).
6066             */
6067            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6068                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6069                          false);
6070
6071            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6072                // In a fixed-height view, so use our new text layout.
6073                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6074                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6075                    invalidate();
6076                    return;
6077                }
6078
6079                // Dynamic height, but height has stayed the same,
6080                // so use our new text layout.
6081                if (mLayout.getHeight() == oldht &&
6082                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6083                    invalidate();
6084                    return;
6085                }
6086            }
6087
6088            // We lose: the height has changed and we have a dynamic height.
6089            // Request a new view layout using our new text layout.
6090            requestLayout();
6091            invalidate();
6092        } else {
6093            // Dynamic width, so we have no choice but to request a new
6094            // view layout with a new text layout.
6095
6096            nullLayouts();
6097            requestLayout();
6098            invalidate();
6099        }
6100    }
6101
6102    /**
6103     * Returns true if anything changed.
6104     */
6105    private boolean bringTextIntoView() {
6106        int line = 0;
6107        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6108            line = mLayout.getLineCount() - 1;
6109        }
6110
6111        Layout.Alignment a = mLayout.getParagraphAlignment(line);
6112        int dir = mLayout.getParagraphDirection(line);
6113        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6114        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6115        int ht = mLayout.getHeight();
6116
6117        int scrollx, scrolly;
6118
6119        if (a == Layout.Alignment.ALIGN_CENTER) {
6120            /*
6121             * Keep centered if possible, or, if it is too wide to fit,
6122             * keep leading edge in view.
6123             */
6124
6125            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6126            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6127
6128            if (right - left < hspace) {
6129                scrollx = (right + left) / 2 - hspace / 2;
6130            } else {
6131                if (dir < 0) {
6132                    scrollx = right - hspace;
6133                } else {
6134                    scrollx = left;
6135                }
6136            }
6137        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
6138            /*
6139             * Keep leading edge in view.
6140             */
6141
6142            if (dir < 0) {
6143                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6144                scrollx = right - hspace;
6145            } else {
6146                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6147            }
6148        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
6149            /*
6150             * Keep trailing edge in view.
6151             */
6152
6153            if (dir < 0) {
6154                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6155            } else {
6156                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6157                scrollx = right - hspace;
6158            }
6159        }
6160
6161        if (ht < vspace) {
6162            scrolly = 0;
6163        } else {
6164            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6165                scrolly = ht - vspace;
6166            } else {
6167                scrolly = 0;
6168            }
6169        }
6170
6171        if (scrollx != mScrollX || scrolly != mScrollY) {
6172            scrollTo(scrollx, scrolly);
6173            return true;
6174        } else {
6175            return false;
6176        }
6177    }
6178
6179    /**
6180     * Move the point, specified by the offset, into the view if it is needed.
6181     * This has to be called after layout. Returns true if anything changed.
6182     */
6183    public boolean bringPointIntoView(int offset) {
6184        boolean changed = false;
6185
6186        int line = mLayout.getLineForOffset(offset);
6187
6188        // FIXME: Is it okay to truncate this, or should we round?
6189        final int x = (int)mLayout.getPrimaryHorizontal(offset);
6190        final int top = mLayout.getLineTop(line);
6191        final int bottom = mLayout.getLineTop(line + 1);
6192
6193        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6194        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6195        int ht = mLayout.getHeight();
6196
6197        int grav;
6198
6199        switch (mLayout.getParagraphAlignment(line)) {
6200            case ALIGN_NORMAL:
6201                grav = 1;
6202                break;
6203
6204            case ALIGN_OPPOSITE:
6205                grav = -1;
6206                break;
6207
6208            default:
6209                grav = 0;
6210        }
6211
6212        grav *= mLayout.getParagraphDirection(line);
6213
6214        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6215        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6216
6217        int hslack = (bottom - top) / 2;
6218        int vslack = hslack;
6219
6220        if (vslack > vspace / 4)
6221            vslack = vspace / 4;
6222        if (hslack > hspace / 4)
6223            hslack = hspace / 4;
6224
6225        int hs = mScrollX;
6226        int vs = mScrollY;
6227
6228        if (top - vs < vslack)
6229            vs = top - vslack;
6230        if (bottom - vs > vspace - vslack)
6231            vs = bottom - (vspace - vslack);
6232        if (ht - vs < vspace)
6233            vs = ht - vspace;
6234        if (0 - vs > 0)
6235            vs = 0;
6236
6237        if (grav != 0) {
6238            if (x - hs < hslack) {
6239                hs = x - hslack;
6240            }
6241            if (x - hs > hspace - hslack) {
6242                hs = x - (hspace - hslack);
6243            }
6244        }
6245
6246        if (grav < 0) {
6247            if (left - hs > 0)
6248                hs = left;
6249            if (right - hs < hspace)
6250                hs = right - hspace;
6251        } else if (grav > 0) {
6252            if (right - hs < hspace)
6253                hs = right - hspace;
6254            if (left - hs > 0)
6255                hs = left;
6256        } else /* grav == 0 */ {
6257            if (right - left <= hspace) {
6258                /*
6259                 * If the entire text fits, center it exactly.
6260                 */
6261                hs = left - (hspace - (right - left)) / 2;
6262            } else if (x > right - hslack) {
6263                /*
6264                 * If we are near the right edge, keep the right edge
6265                 * at the edge of the view.
6266                 */
6267                hs = right - hspace;
6268            } else if (x < left + hslack) {
6269                /*
6270                 * If we are near the left edge, keep the left edge
6271                 * at the edge of the view.
6272                 */
6273                hs = left;
6274            } else if (left > hs) {
6275                /*
6276                 * Is there whitespace visible at the left?  Fix it if so.
6277                 */
6278                hs = left;
6279            } else if (right < hs + hspace) {
6280                /*
6281                 * Is there whitespace visible at the right?  Fix it if so.
6282                 */
6283                hs = right - hspace;
6284            } else {
6285                /*
6286                 * Otherwise, float as needed.
6287                 */
6288                if (x - hs < hslack) {
6289                    hs = x - hslack;
6290                }
6291                if (x - hs > hspace - hslack) {
6292                    hs = x - (hspace - hslack);
6293                }
6294            }
6295        }
6296
6297        if (hs != mScrollX || vs != mScrollY) {
6298            if (mScroller == null) {
6299                scrollTo(hs, vs);
6300            } else {
6301                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6302                int dx = hs - mScrollX;
6303                int dy = vs - mScrollY;
6304
6305                if (duration > ANIMATED_SCROLL_GAP) {
6306                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
6307                    awakenScrollBars(mScroller.getDuration());
6308                    invalidate();
6309                } else {
6310                    if (!mScroller.isFinished()) {
6311                        mScroller.abortAnimation();
6312                    }
6313
6314                    scrollBy(dx, dy);
6315                }
6316
6317                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6318            }
6319
6320            changed = true;
6321        }
6322
6323        if (isFocused()) {
6324            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6325            // requestRectangleOnScreen() is in terms of content coordinates.
6326
6327            if (mTempRect == null) mTempRect = new Rect();
6328            mTempRect.set(x, top, x + 1, bottom);
6329            getInterestingRect(mTempRect, line);
6330            mTempRect.offset(mScrollX, mScrollY);
6331
6332            if (requestRectangleOnScreen(mTempRect)) {
6333                changed = true;
6334            }
6335        }
6336
6337        return changed;
6338    }
6339
6340    /**
6341     * Move the cursor, if needed, so that it is at an offset that is visible
6342     * to the user.  This will not move the cursor if it represents more than
6343     * one character (a selection range).  This will only work if the
6344     * TextView contains spannable text; otherwise it will do nothing.
6345     *
6346     * @return True if the cursor was actually moved, false otherwise.
6347     */
6348    public boolean moveCursorToVisibleOffset() {
6349        if (!(mText instanceof Spannable)) {
6350            return false;
6351        }
6352        int start = getSelectionStart();
6353        int end = getSelectionEnd();
6354        if (start != end) {
6355            return false;
6356        }
6357
6358        // First: make sure the line is visible on screen:
6359
6360        int line = mLayout.getLineForOffset(start);
6361
6362        final int top = mLayout.getLineTop(line);
6363        final int bottom = mLayout.getLineTop(line + 1);
6364        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6365        int vslack = (bottom - top) / 2;
6366        if (vslack > vspace / 4)
6367            vslack = vspace / 4;
6368        final int vs = mScrollY;
6369
6370        if (top < (vs+vslack)) {
6371            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6372        } else if (bottom > (vspace+vs-vslack)) {
6373            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6374        }
6375
6376        // Next: make sure the character is visible on screen:
6377
6378        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6379        final int hs = mScrollX;
6380        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6381        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
6382
6383        // line might contain bidirectional text
6384        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6385        final int highChar = leftChar > rightChar ? leftChar : rightChar;
6386
6387        int newStart = start;
6388        if (newStart < lowChar) {
6389            newStart = lowChar;
6390        } else if (newStart > highChar) {
6391            newStart = highChar;
6392        }
6393
6394        if (newStart != start) {
6395            Selection.setSelection((Spannable)mText, newStart);
6396            return true;
6397        }
6398
6399        return false;
6400    }
6401
6402    @Override
6403    public void computeScroll() {
6404        if (mScroller != null) {
6405            if (mScroller.computeScrollOffset()) {
6406                mScrollX = mScroller.getCurrX();
6407                mScrollY = mScroller.getCurrY();
6408                invalidateParentCaches();
6409                postInvalidate();  // So we draw again
6410            }
6411        }
6412    }
6413
6414    private void getInterestingRect(Rect r, int line) {
6415        convertFromViewportToContentCoordinates(r);
6416
6417        // Rectangle can can be expanded on first and last line to take
6418        // padding into account.
6419        // TODO Take left/right padding into account too?
6420        if (line == 0) r.top -= getExtendedPaddingTop();
6421        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6422    }
6423
6424    private void convertFromViewportToContentCoordinates(Rect r) {
6425        final int horizontalOffset = viewportToContentHorizontalOffset();
6426        r.left += horizontalOffset;
6427        r.right += horizontalOffset;
6428
6429        final int verticalOffset = viewportToContentVerticalOffset();
6430        r.top += verticalOffset;
6431        r.bottom += verticalOffset;
6432    }
6433
6434    private int viewportToContentHorizontalOffset() {
6435        return getCompoundPaddingLeft() - mScrollX;
6436    }
6437
6438    private int viewportToContentVerticalOffset() {
6439        int offset = getExtendedPaddingTop() - mScrollY;
6440        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6441            offset += getVerticalOffset(false);
6442        }
6443        return offset;
6444    }
6445
6446    @Override
6447    public void debug(int depth) {
6448        super.debug(depth);
6449
6450        String output = debugIndent(depth);
6451        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6452                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6453                + "} ";
6454
6455        if (mText != null) {
6456
6457            output += "mText=\"" + mText + "\" ";
6458            if (mLayout != null) {
6459                output += "mLayout width=" + mLayout.getWidth()
6460                        + " height=" + mLayout.getHeight();
6461            }
6462        } else {
6463            output += "mText=NULL";
6464        }
6465        Log.d(VIEW_LOG_TAG, output);
6466    }
6467
6468    /**
6469     * Convenience for {@link Selection#getSelectionStart}.
6470     */
6471    @ViewDebug.ExportedProperty(category = "text")
6472    public int getSelectionStart() {
6473        return Selection.getSelectionStart(getText());
6474    }
6475
6476    /**
6477     * Convenience for {@link Selection#getSelectionEnd}.
6478     */
6479    @ViewDebug.ExportedProperty(category = "text")
6480    public int getSelectionEnd() {
6481        return Selection.getSelectionEnd(getText());
6482    }
6483
6484    /**
6485     * Return true iff there is a selection inside this text view.
6486     */
6487    public boolean hasSelection() {
6488        final int selectionStart = getSelectionStart();
6489        final int selectionEnd = getSelectionEnd();
6490
6491        return selectionStart >= 0 && selectionStart != selectionEnd;
6492    }
6493
6494    /**
6495     * Sets the properties of this field (lines, horizontally scrolling,
6496     * transformation method) to be for a single-line input.
6497     *
6498     * @attr ref android.R.styleable#TextView_singleLine
6499     */
6500    public void setSingleLine() {
6501        setSingleLine(true);
6502    }
6503
6504    /**
6505     * If true, sets the properties of this field (number of lines, horizontally scrolling,
6506     * transformation method) to be for a single-line input; if false, restores these to the default
6507     * conditions.
6508     *
6509     * Note that the default conditions are not necessarily those that were in effect prior this
6510     * method, and you may want to reset these properties to your custom values.
6511     *
6512     * @attr ref android.R.styleable#TextView_singleLine
6513     */
6514    @android.view.RemotableViewMethod
6515    public void setSingleLine(boolean singleLine) {
6516        // Could be used, but may break backward compatibility.
6517        // if (mSingleLine == singleLine) return;
6518        setInputTypeSingleLine(singleLine);
6519        applySingleLine(singleLine, true, true);
6520    }
6521
6522    /**
6523     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
6524     * @param singleLine
6525     */
6526    private void setInputTypeSingleLine(boolean singleLine) {
6527        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6528            if (singleLine) {
6529                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6530            } else {
6531                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6532            }
6533        }
6534    }
6535
6536    private void applySingleLine(boolean singleLine, boolean applyTransformation,
6537            boolean changeMaxLines) {
6538        mSingleLine = singleLine;
6539        if (singleLine) {
6540            setLines(1);
6541            setHorizontallyScrolling(true);
6542            if (applyTransformation) {
6543                setTransformationMethod(SingleLineTransformationMethod.getInstance());
6544            }
6545        } else {
6546            if (changeMaxLines) {
6547                setMaxLines(Integer.MAX_VALUE);
6548            }
6549            setHorizontallyScrolling(false);
6550            if (applyTransformation) {
6551                setTransformationMethod(null);
6552            }
6553        }
6554    }
6555
6556    /**
6557     * Causes words in the text that are longer than the view is wide
6558     * to be ellipsized instead of broken in the middle.  You may also
6559     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
6560     * to constrain the text to a single line.  Use <code>null</code>
6561     * to turn off ellipsizing.
6562     *
6563     * @attr ref android.R.styleable#TextView_ellipsize
6564     */
6565    public void setEllipsize(TextUtils.TruncateAt where) {
6566        mEllipsize = where;
6567
6568        if (mLayout != null) {
6569            nullLayouts();
6570            requestLayout();
6571            invalidate();
6572        }
6573    }
6574
6575    /**
6576     * Sets how many times to repeat the marquee animation. Only applied if the
6577     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
6578     *
6579     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
6580     */
6581    public void setMarqueeRepeatLimit(int marqueeLimit) {
6582        mMarqueeRepeatLimit = marqueeLimit;
6583    }
6584
6585    /**
6586     * Returns where, if anywhere, words that are longer than the view
6587     * is wide should be ellipsized.
6588     */
6589    @ViewDebug.ExportedProperty
6590    public TextUtils.TruncateAt getEllipsize() {
6591        return mEllipsize;
6592    }
6593
6594    /**
6595     * Set the TextView so that when it takes focus, all the text is
6596     * selected.
6597     *
6598     * @attr ref android.R.styleable#TextView_selectAllOnFocus
6599     */
6600    @android.view.RemotableViewMethod
6601    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
6602        mSelectAllOnFocus = selectAllOnFocus;
6603
6604        if (selectAllOnFocus && !(mText instanceof Spannable)) {
6605            setText(mText, BufferType.SPANNABLE);
6606        }
6607    }
6608
6609    /**
6610     * Set whether the cursor is visible.  The default is true.
6611     *
6612     * @attr ref android.R.styleable#TextView_cursorVisible
6613     */
6614    @android.view.RemotableViewMethod
6615    public void setCursorVisible(boolean visible) {
6616        if (mCursorVisible != visible) {
6617            mCursorVisible = visible;
6618            invalidate();
6619
6620            makeBlink();
6621
6622            // InsertionPointCursorController depends on mCursorVisible
6623            prepareCursorControllers();
6624        }
6625    }
6626
6627    private boolean isCursorVisible() {
6628        return mCursorVisible && isTextEditable();
6629    }
6630
6631    private boolean canMarquee() {
6632        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
6633        return width > 0 && mLayout.getLineWidth(0) > width;
6634    }
6635
6636    private void startMarquee() {
6637        // Do not ellipsize EditText
6638        if (mInput != null) return;
6639
6640        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
6641            return;
6642        }
6643
6644        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
6645                getLineCount() == 1 && canMarquee()) {
6646
6647            if (mMarquee == null) mMarquee = new Marquee(this);
6648            mMarquee.start(mMarqueeRepeatLimit);
6649        }
6650    }
6651
6652    private void stopMarquee() {
6653        if (mMarquee != null && !mMarquee.isStopped()) {
6654            mMarquee.stop();
6655        }
6656    }
6657
6658    private void startStopMarquee(boolean start) {
6659        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6660            if (start) {
6661                startMarquee();
6662            } else {
6663                stopMarquee();
6664            }
6665        }
6666    }
6667
6668    private static final class Marquee extends Handler {
6669        // TODO: Add an option to configure this
6670        private static final float MARQUEE_DELTA_MAX = 0.07f;
6671        private static final int MARQUEE_DELAY = 1200;
6672        private static final int MARQUEE_RESTART_DELAY = 1200;
6673        private static final int MARQUEE_RESOLUTION = 1000 / 30;
6674        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
6675
6676        private static final byte MARQUEE_STOPPED = 0x0;
6677        private static final byte MARQUEE_STARTING = 0x1;
6678        private static final byte MARQUEE_RUNNING = 0x2;
6679
6680        private static final int MESSAGE_START = 0x1;
6681        private static final int MESSAGE_TICK = 0x2;
6682        private static final int MESSAGE_RESTART = 0x3;
6683
6684        private final WeakReference<TextView> mView;
6685
6686        private byte mStatus = MARQUEE_STOPPED;
6687        private final float mScrollUnit;
6688        private float mMaxScroll;
6689        float mMaxFadeScroll;
6690        private float mGhostStart;
6691        private float mGhostOffset;
6692        private float mFadeStop;
6693        private int mRepeatLimit;
6694
6695        float mScroll;
6696
6697        Marquee(TextView v) {
6698            final float density = v.getContext().getResources().getDisplayMetrics().density;
6699            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
6700            mView = new WeakReference<TextView>(v);
6701        }
6702
6703        @Override
6704        public void handleMessage(Message msg) {
6705            switch (msg.what) {
6706                case MESSAGE_START:
6707                    mStatus = MARQUEE_RUNNING;
6708                    tick();
6709                    break;
6710                case MESSAGE_TICK:
6711                    tick();
6712                    break;
6713                case MESSAGE_RESTART:
6714                    if (mStatus == MARQUEE_RUNNING) {
6715                        if (mRepeatLimit >= 0) {
6716                            mRepeatLimit--;
6717                        }
6718                        start(mRepeatLimit);
6719                    }
6720                    break;
6721            }
6722        }
6723
6724        void tick() {
6725            if (mStatus != MARQUEE_RUNNING) {
6726                return;
6727            }
6728
6729            removeMessages(MESSAGE_TICK);
6730
6731            final TextView textView = mView.get();
6732            if (textView != null && (textView.isFocused() || textView.isSelected())) {
6733                mScroll += mScrollUnit;
6734                if (mScroll > mMaxScroll) {
6735                    mScroll = mMaxScroll;
6736                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
6737                } else {
6738                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
6739                }
6740                textView.invalidate();
6741            }
6742        }
6743
6744        void stop() {
6745            mStatus = MARQUEE_STOPPED;
6746            removeMessages(MESSAGE_START);
6747            removeMessages(MESSAGE_RESTART);
6748            removeMessages(MESSAGE_TICK);
6749            resetScroll();
6750        }
6751
6752        private void resetScroll() {
6753            mScroll = 0.0f;
6754            final TextView textView = mView.get();
6755            if (textView != null) textView.invalidate();
6756        }
6757
6758        void start(int repeatLimit) {
6759            if (repeatLimit == 0) {
6760                stop();
6761                return;
6762            }
6763            mRepeatLimit = repeatLimit;
6764            final TextView textView = mView.get();
6765            if (textView != null && textView.mLayout != null) {
6766                mStatus = MARQUEE_STARTING;
6767                mScroll = 0.0f;
6768                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
6769                        textView.getCompoundPaddingRight();
6770                final float lineWidth = textView.mLayout.getLineWidth(0);
6771                final float gap = textWidth / 3.0f;
6772                mGhostStart = lineWidth - textWidth + gap;
6773                mMaxScroll = mGhostStart + textWidth;
6774                mGhostOffset = lineWidth + gap;
6775                mFadeStop = lineWidth + textWidth / 6.0f;
6776                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
6777
6778                textView.invalidate();
6779                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
6780            }
6781        }
6782
6783        float getGhostOffset() {
6784            return mGhostOffset;
6785        }
6786
6787        boolean shouldDrawLeftFade() {
6788            return mScroll <= mFadeStop;
6789        }
6790
6791        boolean shouldDrawGhost() {
6792            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
6793        }
6794
6795        boolean isRunning() {
6796            return mStatus == MARQUEE_RUNNING;
6797        }
6798
6799        boolean isStopped() {
6800            return mStatus == MARQUEE_STOPPED;
6801        }
6802    }
6803
6804    /**
6805     * This method is called when the text is changed, in case any subclasses
6806     * would like to know.
6807     *
6808     * Within <code>text</code>, the <code>lengthAfter</code> characters
6809     * beginning at <code>start</code> have just replaced old text that had
6810     * length <code>lengthBefore</code>. It is an error to attempt to make
6811     * changes to <code>text</code> from this callback.
6812     *
6813     * @param text The text the TextView is displaying
6814     * @param start The offset of the start of the range of the text that was
6815     * modified
6816     * @param lengthBefore The length of the former text that has been replaced
6817     * @param lengthAfter The length of the replacement modified text
6818     */
6819    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
6820        // intentionally empty
6821    }
6822
6823    /**
6824     * This method is called when the selection has changed, in case any
6825     * subclasses would like to know.
6826     *
6827     * @param selStart The new selection start location.
6828     * @param selEnd The new selection end location.
6829     */
6830    protected void onSelectionChanged(int selStart, int selEnd) {
6831        // intentionally empty
6832    }
6833
6834    /**
6835     * Adds a TextWatcher to the list of those whose methods are called
6836     * whenever this TextView's text changes.
6837     * <p>
6838     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6839     * not called after {@link #setText} calls.  Now, doing {@link #setText}
6840     * if there are any text changed listeners forces the buffer type to
6841     * Editable if it would not otherwise be and does call this method.
6842     */
6843    public void addTextChangedListener(TextWatcher watcher) {
6844        if (mListeners == null) {
6845            mListeners = new ArrayList<TextWatcher>();
6846        }
6847
6848        mListeners.add(watcher);
6849    }
6850
6851    /**
6852     * Removes the specified TextWatcher from the list of those whose
6853     * methods are called
6854     * whenever this TextView's text changes.
6855     */
6856    public void removeTextChangedListener(TextWatcher watcher) {
6857        if (mListeners != null) {
6858            int i = mListeners.indexOf(watcher);
6859
6860            if (i >= 0) {
6861                mListeners.remove(i);
6862            }
6863        }
6864    }
6865
6866    private void sendBeforeTextChanged(CharSequence text, int start, int before,
6867                                   int after) {
6868        if (mListeners != null) {
6869            final ArrayList<TextWatcher> list = mListeners;
6870            final int count = list.size();
6871            for (int i = 0; i < count; i++) {
6872                list.get(i).beforeTextChanged(text, start, before, after);
6873            }
6874        }
6875    }
6876
6877    /**
6878     * Not private so it can be called from an inner class without going
6879     * through a thunk.
6880     */
6881    void sendOnTextChanged(CharSequence text, int start, int before,
6882                                   int after) {
6883        if (mListeners != null) {
6884            final ArrayList<TextWatcher> list = mListeners;
6885            final int count = list.size();
6886            for (int i = 0; i < count; i++) {
6887                list.get(i).onTextChanged(text, start, before, after);
6888            }
6889        }
6890    }
6891
6892    /**
6893     * Not private so it can be called from an inner class without going
6894     * through a thunk.
6895     */
6896    void sendAfterTextChanged(Editable text) {
6897        if (mListeners != null) {
6898            final ArrayList<TextWatcher> list = mListeners;
6899            final int count = list.size();
6900            for (int i = 0; i < count; i++) {
6901                list.get(i).afterTextChanged(text);
6902            }
6903        }
6904    }
6905
6906    /**
6907     * Not private so it can be called from an inner class without going
6908     * through a thunk.
6909     */
6910    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
6911        final InputMethodState ims = mInputMethodState;
6912        if (ims == null || ims.mBatchEditNesting == 0) {
6913            updateAfterEdit();
6914        }
6915        if (ims != null) {
6916            ims.mContentChanged = true;
6917            if (ims.mChangedStart < 0) {
6918                ims.mChangedStart = start;
6919                ims.mChangedEnd = start+before;
6920            } else {
6921                ims.mChangedStart = Math.min(ims.mChangedStart, start);
6922                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
6923            }
6924            ims.mChangedDelta += after-before;
6925        }
6926
6927        sendOnTextChanged(buffer, start, before, after);
6928        onTextChanged(buffer, start, before, after);
6929
6930        // Hide the controllers if the amount of content changed
6931        if (before != after) {
6932            hideControllers();
6933        }
6934    }
6935
6936    /**
6937     * Not private so it can be called from an inner class without going
6938     * through a thunk.
6939     */
6940    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
6941        // XXX Make the start and end move together if this ends up
6942        // spending too much time invalidating.
6943
6944        boolean selChanged = false;
6945        int newSelStart=-1, newSelEnd=-1;
6946
6947        final InputMethodState ims = mInputMethodState;
6948
6949        if (what == Selection.SELECTION_END) {
6950            mHighlightPathBogus = true;
6951            selChanged = true;
6952            newSelEnd = newStart;
6953
6954            if (!isFocused()) {
6955                mSelectionMoved = true;
6956            }
6957
6958            if (oldStart >= 0 || newStart >= 0) {
6959                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6960                registerForPreDraw();
6961                makeBlink();
6962            }
6963        }
6964
6965        if (what == Selection.SELECTION_START) {
6966            mHighlightPathBogus = true;
6967            selChanged = true;
6968            newSelStart = newStart;
6969
6970            if (!isFocused()) {
6971                mSelectionMoved = true;
6972            }
6973
6974            if (oldStart >= 0 || newStart >= 0) {
6975                int end = Selection.getSelectionEnd(buf);
6976                invalidateCursor(end, oldStart, newStart);
6977            }
6978        }
6979
6980        if (selChanged) {
6981            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6982                if (newSelStart < 0) {
6983                    newSelStart = Selection.getSelectionStart(buf);
6984                }
6985                if (newSelEnd < 0) {
6986                    newSelEnd = Selection.getSelectionEnd(buf);
6987                }
6988                onSelectionChanged(newSelStart, newSelEnd);
6989            }
6990        }
6991
6992        if (what instanceof UpdateAppearance ||
6993            what instanceof ParagraphStyle) {
6994            if (ims == null || ims.mBatchEditNesting == 0) {
6995                invalidate();
6996                mHighlightPathBogus = true;
6997                checkForResize();
6998            } else {
6999                ims.mContentChanged = true;
7000            }
7001        }
7002
7003        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7004            mHighlightPathBogus = true;
7005            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7006                ims.mSelectionModeChanged = true;
7007            }
7008
7009            if (Selection.getSelectionStart(buf) >= 0) {
7010                if (ims == null || ims.mBatchEditNesting == 0) {
7011                    invalidateCursor();
7012                } else {
7013                    ims.mCursorChanged = true;
7014                }
7015            }
7016        }
7017
7018        if (what instanceof ParcelableSpan) {
7019            // If this is a span that can be sent to a remote process,
7020            // the current extract editor would be interested in it.
7021            if (ims != null && ims.mExtracting != null) {
7022                if (ims.mBatchEditNesting != 0) {
7023                    if (oldStart >= 0) {
7024                        if (ims.mChangedStart > oldStart) {
7025                            ims.mChangedStart = oldStart;
7026                        }
7027                        if (ims.mChangedStart > oldEnd) {
7028                            ims.mChangedStart = oldEnd;
7029                        }
7030                    }
7031                    if (newStart >= 0) {
7032                        if (ims.mChangedStart > newStart) {
7033                            ims.mChangedStart = newStart;
7034                        }
7035                        if (ims.mChangedStart > newEnd) {
7036                            ims.mChangedStart = newEnd;
7037                        }
7038                    }
7039                } else {
7040                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7041                            + oldStart + "-" + oldEnd + ","
7042                            + newStart + "-" + newEnd + what);
7043                    ims.mContentChanged = true;
7044                }
7045            }
7046        }
7047    }
7048
7049    private class ChangeWatcher
7050    implements TextWatcher, SpanWatcher {
7051
7052        private CharSequence mBeforeText;
7053
7054        public void beforeTextChanged(CharSequence buffer, int start,
7055                                      int before, int after) {
7056            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
7057                    + " before=" + before + " after=" + after + ": " + buffer);
7058
7059            if (AccessibilityManager.getInstance(mContext).isEnabled()
7060                    && !isPasswordInputType(mInputType)
7061                    && !hasPasswordTransformationMethod()) {
7062                mBeforeText = buffer.toString();
7063            }
7064
7065            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
7066        }
7067
7068        public void onTextChanged(CharSequence buffer, int start,
7069                                  int before, int after) {
7070            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
7071                    + " before=" + before + " after=" + after + ": " + buffer);
7072            TextView.this.handleTextChanged(buffer, start, before, after);
7073
7074            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
7075                    (isFocused() || isSelected() &&
7076                    isShown())) {
7077                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
7078                mBeforeText = null;
7079            }
7080        }
7081
7082        public void afterTextChanged(Editable buffer) {
7083            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
7084            TextView.this.sendAfterTextChanged(buffer);
7085
7086            if (MetaKeyKeyListener.getMetaState(buffer,
7087                                 MetaKeyKeyListener.META_SELECTING) != 0) {
7088                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
7089            }
7090        }
7091
7092        public void onSpanChanged(Spannable buf,
7093                                  Object what, int s, int e, int st, int en) {
7094            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
7095                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
7096            TextView.this.spanChange(buf, what, s, st, e, en);
7097        }
7098
7099        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
7100            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
7101                    + " what=" + what + ": " + buf);
7102            TextView.this.spanChange(buf, what, -1, s, -1, e);
7103        }
7104
7105        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
7106            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
7107                    + " what=" + what + ": " + buf);
7108            TextView.this.spanChange(buf, what, s, -1, e, -1);
7109        }
7110    }
7111
7112    /**
7113     * @hide
7114     */
7115    @Override
7116    public void dispatchFinishTemporaryDetach() {
7117        mDispatchTemporaryDetach = true;
7118        super.dispatchFinishTemporaryDetach();
7119        mDispatchTemporaryDetach = false;
7120    }
7121
7122    @Override
7123    public void onStartTemporaryDetach() {
7124        super.onStartTemporaryDetach();
7125        // Only track when onStartTemporaryDetach() is called directly,
7126        // usually because this instance is an editable field in a list
7127        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
7128
7129        // Because of View recycling in ListView, there is no easy way to know when a TextView with
7130        // selection becomes visible again. Until a better solution is found, stop text selection
7131        // mode (if any) as soon as this TextView is recycled.
7132        stopSelectionActionMode();
7133    }
7134
7135    @Override
7136    public void onFinishTemporaryDetach() {
7137        super.onFinishTemporaryDetach();
7138        // Only track when onStartTemporaryDetach() is called directly,
7139        // usually because this instance is an editable field in a list
7140        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
7141    }
7142
7143    @Override
7144    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7145        if (mTemporaryDetach) {
7146            // If we are temporarily in the detach state, then do nothing.
7147            super.onFocusChanged(focused, direction, previouslyFocusedRect);
7148            return;
7149        }
7150
7151        mShowCursor = SystemClock.uptimeMillis();
7152
7153        ensureEndedBatchEdit();
7154
7155        if (focused) {
7156            int selStart = getSelectionStart();
7157            int selEnd = getSelectionEnd();
7158
7159            // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
7160            // mode for these, unless there was a specific selection already started.
7161            final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
7162                    selEnd == mText.length();
7163            mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
7164
7165            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
7166                // If a tap was used to give focus to that view, move cursor at tap position.
7167                // Has to be done before onTakeFocus, which can be overloaded.
7168                final int lastTapPosition = getLastTapPosition();
7169                if (lastTapPosition >= 0) {
7170                    Selection.setSelection((Spannable) mText, lastTapPosition);
7171                }
7172
7173                if (mMovement != null) {
7174                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
7175                }
7176
7177                // The DecorView does not have focus when the 'Done' ExtractEditText button is
7178                // pressed. Since it is the ViewAncestor's mView, it requests focus before
7179                // ExtractEditText clears focus, which gives focus to the ExtractEditText.
7180                // This special case ensure that we keep current selection in that case.
7181                // It would be better to know why the DecorView does not have focus at that time.
7182                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
7183                        selStart >= 0 && selEnd >= 0) {
7184                    /*
7185                     * Someone intentionally set the selection, so let them
7186                     * do whatever it is that they wanted to do instead of
7187                     * the default on-focus behavior.  We reset the selection
7188                     * here instead of just skipping the onTakeFocus() call
7189                     * because some movement methods do something other than
7190                     * just setting the selection in theirs and we still
7191                     * need to go through that path.
7192                     */
7193                    Selection.setSelection((Spannable) mText, selStart, selEnd);
7194                }
7195
7196                if (mSelectAllOnFocus) {
7197                    selectAll();
7198                }
7199
7200                mTouchFocusSelected = true;
7201            }
7202
7203            mFrozenWithFocus = false;
7204            mSelectionMoved = false;
7205
7206            if (mText instanceof Spannable) {
7207                Spannable sp = (Spannable) mText;
7208                MetaKeyKeyListener.resetMetaState(sp);
7209            }
7210
7211            makeBlink();
7212
7213            if (mError != null) {
7214                showError();
7215            }
7216        } else {
7217            if (mError != null) {
7218                hideError();
7219            }
7220            // Don't leave us in the middle of a batch edit.
7221            onEndBatchEdit();
7222
7223            if (this instanceof ExtractEditText) {
7224                // terminateTextSelectionMode removes selection, which we want to keep when
7225                // ExtractEditText goes out of focus.
7226                final int selStart = getSelectionStart();
7227                final int selEnd = getSelectionEnd();
7228                hideControllers();
7229                Selection.setSelection((Spannable) mText, selStart, selEnd);
7230            } else {
7231                hideControllers();
7232            }
7233
7234            // No need to create the controller
7235            if (mSelectionModifierCursorController != null) {
7236                mSelectionModifierCursorController.resetTouchOffsets();
7237            }
7238        }
7239
7240        startStopMarquee(focused);
7241
7242        if (mTransformation != null) {
7243            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
7244        }
7245
7246        super.onFocusChanged(focused, direction, previouslyFocusedRect);
7247    }
7248
7249    private int getLastTapPosition() {
7250        // No need to create the controller at that point, no last tap position saved
7251        if (mSelectionModifierCursorController != null) {
7252            int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
7253            if (lastTapPosition >= 0) {
7254                // Safety check, should not be possible.
7255                if (lastTapPosition > mText.length()) {
7256                    Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
7257                            + mText.length() + ")");
7258                    lastTapPosition = mText.length();
7259                }
7260                return lastTapPosition;
7261            }
7262        }
7263
7264        return -1;
7265    }
7266
7267    @Override
7268    public void onWindowFocusChanged(boolean hasWindowFocus) {
7269        super.onWindowFocusChanged(hasWindowFocus);
7270
7271        if (hasWindowFocus) {
7272            if (mBlink != null) {
7273                mBlink.uncancel();
7274                makeBlink();
7275            }
7276        } else {
7277            if (mBlink != null) {
7278                mBlink.cancel();
7279            }
7280            // Don't leave us in the middle of a batch edit.
7281            onEndBatchEdit();
7282            if (mInputContentType != null) {
7283                mInputContentType.enterDown = false;
7284            }
7285            hideControllers();
7286            removeAllSuggestionSpans();
7287        }
7288
7289        startStopMarquee(hasWindowFocus);
7290    }
7291
7292    private void removeAllSuggestionSpans() {
7293        if (mText instanceof Editable) {
7294            Editable editable = ((Editable) mText);
7295            SuggestionSpan[] spans = editable.getSpans(0, mText.length(), SuggestionSpan.class);
7296            final int length = spans.length;
7297            for (int i = 0; i < length; i++) {
7298                editable.removeSpan(spans[i]);
7299            }
7300        }
7301    }
7302
7303    @Override
7304    protected void onVisibilityChanged(View changedView, int visibility) {
7305        super.onVisibilityChanged(changedView, visibility);
7306        if (visibility != VISIBLE) {
7307            hideControllers();
7308        }
7309    }
7310
7311    /**
7312     * Use {@link BaseInputConnection#removeComposingSpans
7313     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7314     * state from this text view.
7315     */
7316    public void clearComposingText() {
7317        if (mText instanceof Spannable) {
7318            BaseInputConnection.removeComposingSpans((Spannable)mText);
7319        }
7320    }
7321
7322    @Override
7323    public void setSelected(boolean selected) {
7324        boolean wasSelected = isSelected();
7325
7326        super.setSelected(selected);
7327
7328        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7329            if (selected) {
7330                startMarquee();
7331            } else {
7332                stopMarquee();
7333            }
7334        }
7335    }
7336
7337    @Override
7338    public boolean onTouchEvent(MotionEvent event) {
7339        final int action = event.getActionMasked();
7340
7341        if (hasInsertionController()) {
7342            getInsertionController().onTouchEvent(event);
7343        }
7344        if (hasSelectionController()) {
7345            getSelectionController().onTouchEvent(event);
7346        }
7347
7348        if (action == MotionEvent.ACTION_DOWN) {
7349            mLastDownPositionX = event.getX();
7350            mLastDownPositionY = event.getY();
7351
7352            // Reset this state; it will be re-set if super.onTouchEvent
7353            // causes focus to move to the view.
7354            mTouchFocusSelected = false;
7355            mIgnoreActionUpEvent = false;
7356        }
7357
7358        final boolean superResult = super.onTouchEvent(event);
7359
7360        /*
7361         * Don't handle the release after a long press, because it will
7362         * move the selection away from whatever the menu action was
7363         * trying to affect.
7364         */
7365        if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7366            mDiscardNextActionUp = false;
7367            return superResult;
7368        }
7369
7370        final boolean touchIsFinished = action == MotionEvent.ACTION_UP && !mIgnoreActionUpEvent &&
7371                isFocused();
7372
7373        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7374                && mText instanceof Spannable && mLayout != null) {
7375            boolean handled = false;
7376
7377            if (mMovement != null) {
7378                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7379            }
7380
7381            if (mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable && touchIsFinished) {
7382                // The LinkMovementMethod which should handle taps on links has not been installed
7383                // to support text selection. We reproduce its behavior here to open links.
7384                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7385                        getSelectionEnd(), ClickableSpan.class);
7386
7387                if (links.length != 0) {
7388                    links[0].onClick(this);
7389                    handled = true;
7390                }
7391            }
7392
7393            if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
7394                // Show the IME, except when selecting in read-only text.
7395                if (!mTextIsSelectable) {
7396                    final InputMethodManager imm = InputMethodManager.peekInstance();
7397                    handled |= imm != null && imm.showSoftInput(this, 0);
7398                }
7399
7400                boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
7401                if (!selectAllGotFocus && hasSelection()) {
7402                    startSelectionActionMode();
7403                } else {
7404                    stopSelectionActionMode();
7405                    hideSuggestions();
7406                    if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) {
7407                        getInsertionController().show();
7408                    }
7409                }
7410            }
7411
7412            if (handled) {
7413                return true;
7414            }
7415        }
7416
7417        return superResult;
7418    }
7419
7420    @Override
7421    public boolean onGenericMotionEvent(MotionEvent event) {
7422        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7423            try {
7424                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7425                    return true;
7426                }
7427            } catch (AbstractMethodError ex) {
7428                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7429                // Ignore its absence in case third party applications implemented the
7430                // interface directly.
7431            }
7432        }
7433        return super.onGenericMotionEvent(event);
7434    }
7435
7436    private void prepareCursorControllers() {
7437        boolean windowSupportsHandles = false;
7438
7439        ViewGroup.LayoutParams params = getRootView().getLayoutParams();
7440        if (params instanceof WindowManager.LayoutParams) {
7441            WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
7442            windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
7443                    || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
7444        }
7445
7446        mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
7447        mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
7448                mLayout != null;
7449
7450        if (!mInsertionControllerEnabled) {
7451            hideInsertionPointCursorController();
7452            if (mInsertionPointCursorController != null) {
7453                mInsertionPointCursorController.onDetached();
7454                mInsertionPointCursorController = null;
7455            }
7456        }
7457
7458        if (!mSelectionControllerEnabled) {
7459            stopSelectionActionMode();
7460            if (mSelectionModifierCursorController != null) {
7461                mSelectionModifierCursorController.onDetached();
7462                mSelectionModifierCursorController = null;
7463            }
7464        }
7465    }
7466
7467    /**
7468     * @return True iff this TextView contains a text that can be edited, or if this is
7469     * a selectable TextView.
7470     */
7471    private boolean isTextEditable() {
7472        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
7473    }
7474
7475    /**
7476     * Returns true, only while processing a touch gesture, if the initial
7477     * touch down event caused focus to move to the text view and as a result
7478     * its selection changed.  Only valid while processing the touch gesture
7479     * of interest.
7480     */
7481    public boolean didTouchFocusSelect() {
7482        return mTouchFocusSelected;
7483    }
7484
7485    @Override
7486    public void cancelLongPress() {
7487        super.cancelLongPress();
7488        mIgnoreActionUpEvent = true;
7489    }
7490
7491    @Override
7492    public boolean onTrackballEvent(MotionEvent event) {
7493        if (mMovement != null && mText instanceof Spannable &&
7494            mLayout != null) {
7495            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7496                return true;
7497            }
7498        }
7499
7500        return super.onTrackballEvent(event);
7501    }
7502
7503    public void setScroller(Scroller s) {
7504        mScroller = s;
7505    }
7506
7507    private static class Blink extends Handler implements Runnable {
7508        private final WeakReference<TextView> mView;
7509        private boolean mCancelled;
7510
7511        public Blink(TextView v) {
7512            mView = new WeakReference<TextView>(v);
7513        }
7514
7515        public void run() {
7516            if (mCancelled) {
7517                return;
7518            }
7519
7520            removeCallbacks(Blink.this);
7521
7522            TextView tv = mView.get();
7523
7524            if (tv != null && tv.shouldBlink()) {
7525                if (tv.mLayout != null) {
7526                    tv.invalidateCursorPath();
7527                }
7528
7529                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
7530            }
7531        }
7532
7533        void cancel() {
7534            if (!mCancelled) {
7535                removeCallbacks(Blink.this);
7536                mCancelled = true;
7537            }
7538        }
7539
7540        void uncancel() {
7541            mCancelled = false;
7542        }
7543    }
7544
7545    /**
7546     * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
7547     */
7548    private boolean shouldBlink() {
7549        if (!isFocused()) return false;
7550
7551        final int start = getSelectionStart();
7552        if (start < 0) return false;
7553
7554        final int end = getSelectionEnd();
7555        if (end < 0) return false;
7556
7557        return start == end;
7558    }
7559
7560    private void makeBlink() {
7561        if (isCursorVisible()) {
7562            if (shouldBlink()) {
7563                mShowCursor = SystemClock.uptimeMillis();
7564                if (mBlink == null) mBlink = new Blink(this);
7565                mBlink.removeCallbacks(mBlink);
7566                mBlink.postAtTime(mBlink, mShowCursor + BLINK);
7567            }
7568        } else {
7569            if (mBlink != null) mBlink.removeCallbacks(mBlink);
7570        }
7571    }
7572
7573    @Override
7574    protected float getLeftFadingEdgeStrength() {
7575        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7576        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7577            if (mMarquee != null && !mMarquee.isStopped()) {
7578                final Marquee marquee = mMarquee;
7579                if (marquee.shouldDrawLeftFade()) {
7580                    return marquee.mScroll / getHorizontalFadingEdgeLength();
7581                } else {
7582                    return 0.0f;
7583                }
7584            } else if (getLineCount() == 1) {
7585                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
7586                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7587                    case Gravity.LEFT:
7588                        return 0.0f;
7589                    case Gravity.RIGHT:
7590                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
7591                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
7592                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7593                    case Gravity.CENTER_HORIZONTAL:
7594                        return 0.0f;
7595                }
7596            }
7597        }
7598        return super.getLeftFadingEdgeStrength();
7599    }
7600
7601    @Override
7602    protected float getRightFadingEdgeStrength() {
7603        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7604        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7605            if (mMarquee != null && !mMarquee.isStopped()) {
7606                final Marquee marquee = mMarquee;
7607                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
7608            } else if (getLineCount() == 1) {
7609                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, isLayoutRtl());
7610                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7611                    case Gravity.LEFT:
7612                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7613                                getCompoundPaddingRight();
7614                        final float lineWidth = mLayout.getLineWidth(0);
7615                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
7616                    case Gravity.RIGHT:
7617                        return 0.0f;
7618                    case Gravity.CENTER_HORIZONTAL:
7619                    case Gravity.FILL_HORIZONTAL:
7620                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7621                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7622                                getHorizontalFadingEdgeLength();
7623                }
7624            }
7625        }
7626        return super.getRightFadingEdgeStrength();
7627    }
7628
7629    @Override
7630    protected int computeHorizontalScrollRange() {
7631        if (mLayout != null) {
7632            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7633                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7634        }
7635
7636        return super.computeHorizontalScrollRange();
7637    }
7638
7639    @Override
7640    protected int computeVerticalScrollRange() {
7641        if (mLayout != null)
7642            return mLayout.getHeight();
7643
7644        return super.computeVerticalScrollRange();
7645    }
7646
7647    @Override
7648    protected int computeVerticalScrollExtent() {
7649        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7650    }
7651
7652    @Override
7653    public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
7654        CharSequence thisText = getText();
7655        if (TextUtils.isEmpty(thisText)) {
7656            return;
7657        }
7658        if (thisText.toString().toLowerCase().contains(text)) {
7659            outViews.add(this);
7660        }
7661    }
7662
7663    public enum BufferType {
7664        NORMAL, SPANNABLE, EDITABLE,
7665    }
7666
7667    /**
7668     * Returns the TextView_textColor attribute from the
7669     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7670     * from the TextView_textAppearance attribute, if TextView_textColor
7671     * was not set directly.
7672     */
7673    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7674        ColorStateList colors;
7675        colors = attrs.getColorStateList(com.android.internal.R.styleable.
7676                                         TextView_textColor);
7677
7678        if (colors == null) {
7679            int ap = attrs.getResourceId(com.android.internal.R.styleable.
7680                                         TextView_textAppearance, -1);
7681            if (ap != -1) {
7682                TypedArray appearance;
7683                appearance = context.obtainStyledAttributes(ap,
7684                                            com.android.internal.R.styleable.TextAppearance);
7685                colors = appearance.getColorStateList(com.android.internal.R.styleable.
7686                                                  TextAppearance_textColor);
7687                appearance.recycle();
7688            }
7689        }
7690
7691        return colors;
7692    }
7693
7694    /**
7695     * Returns the default color from the TextView_textColor attribute
7696     * from the AttributeSet, if set, or the default color from the
7697     * TextAppearance_textColor from the TextView_textAppearance attribute,
7698     * if TextView_textColor was not set directly.
7699     */
7700    public static int getTextColor(Context context,
7701                                   TypedArray attrs,
7702                                   int def) {
7703        ColorStateList colors = getTextColors(context, attrs);
7704
7705        if (colors == null) {
7706            return def;
7707        } else {
7708            return colors.getDefaultColor();
7709        }
7710    }
7711
7712    @Override
7713    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
7714        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7715        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7716            switch (keyCode) {
7717            case KeyEvent.KEYCODE_A:
7718                if (canSelectText()) {
7719                    return onTextContextMenuItem(ID_SELECT_ALL);
7720                }
7721                break;
7722            case KeyEvent.KEYCODE_X:
7723                if (canCut()) {
7724                    return onTextContextMenuItem(ID_CUT);
7725                }
7726                break;
7727            case KeyEvent.KEYCODE_C:
7728                if (canCopy()) {
7729                    return onTextContextMenuItem(ID_COPY);
7730                }
7731                break;
7732            case KeyEvent.KEYCODE_V:
7733                if (canPaste()) {
7734                    return onTextContextMenuItem(ID_PASTE);
7735                }
7736                break;
7737            }
7738        }
7739        return super.onKeyShortcut(keyCode, event);
7740    }
7741
7742    /**
7743     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7744     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
7745     * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient.
7746     */
7747    private boolean canSelectText() {
7748        return hasSelectionController() && mText.length() != 0;
7749    }
7750
7751    /**
7752     * Test based on the <i>intrinsic</i> charateristics of the TextView.
7753     * The text must be spannable and the movement method must allow for arbitary selection.
7754     *
7755     * See also {@link #canSelectText()}.
7756     */
7757    private boolean textCanBeSelected() {
7758        // prepareCursorController() relies on this method.
7759        // If you change this condition, make sure prepareCursorController is called anywhere
7760        // the value of this condition might be changed.
7761        return mText instanceof Spannable && mMovement != null && mMovement.canSelectArbitrarily();
7762    }
7763
7764    private boolean canCut() {
7765        if (hasPasswordTransformationMethod()) {
7766            return false;
7767        }
7768
7769        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
7770            return true;
7771        }
7772
7773        return false;
7774    }
7775
7776    private boolean canCopy() {
7777        if (hasPasswordTransformationMethod()) {
7778            return false;
7779        }
7780
7781        if (mText.length() > 0 && hasSelection()) {
7782            return true;
7783        }
7784
7785        return false;
7786    }
7787
7788    private boolean canPaste() {
7789        return (mText instanceof Editable &&
7790                mInput != null &&
7791                getSelectionStart() >= 0 &&
7792                getSelectionEnd() >= 0 &&
7793                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
7794                hasPrimaryClip());
7795    }
7796
7797    private static long packRangeInLong(int start, int end) {
7798        return (((long) start) << 32) | end;
7799    }
7800
7801    private static int extractRangeStartFromLong(long range) {
7802        return (int) (range >>> 32);
7803    }
7804
7805    private static int extractRangeEndFromLong(long range) {
7806        return (int) (range & 0x00000000FFFFFFFFL);
7807    }
7808
7809    private boolean selectAll() {
7810        final int length = mText.length();
7811        Selection.setSelection((Spannable) mText, 0, length);
7812        return length > 0;
7813    }
7814
7815    /**
7816     * Adjusts selection to the word under last touch offset.
7817     * Return true if the operation was successfully performed.
7818     */
7819    private boolean selectCurrentWord() {
7820        if (!canSelectText()) {
7821            return false;
7822        }
7823
7824        if (hasPasswordTransformationMethod()) {
7825            // Always select all on a password field.
7826            // Cut/copy menu entries are not available for passwords, but being able to select all
7827            // is however useful to delete or paste to replace the entire content.
7828            return selectAll();
7829        }
7830
7831        int klass = mInputType & InputType.TYPE_MASK_CLASS;
7832        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
7833
7834        // Specific text field types: select the entire text for these
7835        if (klass == InputType.TYPE_CLASS_NUMBER ||
7836                klass == InputType.TYPE_CLASS_PHONE ||
7837                klass == InputType.TYPE_CLASS_DATETIME ||
7838                variation == InputType.TYPE_TEXT_VARIATION_URI ||
7839                variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
7840                variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
7841                variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
7842            return selectAll();
7843        }
7844
7845        long lastTouchOffsets = getLastTouchOffsets();
7846        final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
7847        final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
7848
7849        int selectionStart, selectionEnd;
7850
7851        // If a URLSpan (web address, email, phone...) is found at that position, select it.
7852        URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
7853        if (urlSpans.length == 1) {
7854            URLSpan url = urlSpans[0];
7855            selectionStart = ((Spanned) mText).getSpanStart(url);
7856            selectionEnd = ((Spanned) mText).getSpanEnd(url);
7857        } else {
7858            if (mWordIterator == null) {
7859                mWordIterator = new WordIterator();
7860            }
7861            // WordIerator handles text changes, this is a no-op if text in unchanged.
7862            mWordIterator.setCharSequence(mText);
7863
7864            selectionStart = mWordIterator.getBeginning(minOffset);
7865            if (selectionStart == BreakIterator.DONE) return false;
7866
7867            selectionEnd = mWordIterator.getEnd(maxOffset);
7868            if (selectionEnd == BreakIterator.DONE) return false;
7869        }
7870
7871        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
7872        return true;
7873    }
7874
7875    private long getLastTouchOffsets() {
7876        int minOffset, maxOffset;
7877
7878        if (mContextMenuTriggeredByKey) {
7879            minOffset = getSelectionStart();
7880            maxOffset = getSelectionEnd();
7881        } else {
7882            SelectionModifierCursorController selectionController = getSelectionController();
7883            minOffset = selectionController.getMinTouchOffset();
7884            maxOffset = selectionController.getMaxTouchOffset();
7885        }
7886
7887        return packRangeInLong(minOffset, maxOffset);
7888    }
7889
7890    @Override
7891    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
7892        super.onPopulateAccessibilityEvent(event);
7893
7894        final boolean isPassword = hasPasswordTransformationMethod();
7895        if (!isPassword) {
7896            CharSequence text = getText();
7897            if (TextUtils.isEmpty(text)) {
7898                text = getHint();
7899            }
7900            if (!TextUtils.isEmpty(text)) {
7901                event.getText().add(text);
7902            }
7903        }
7904    }
7905
7906    @Override
7907    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
7908        super.onInitializeAccessibilityEvent(event);
7909
7910        final boolean isPassword = hasPasswordTransformationMethod();
7911        event.setPassword(isPassword);
7912    }
7913
7914    @Override
7915    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
7916        super.onInitializeAccessibilityNodeInfo(info);
7917
7918        final boolean isPassword = hasPasswordTransformationMethod();
7919        if (!isPassword) {
7920            info.setText(getText());
7921        }
7922        info.setPassword(isPassword);
7923    }
7924
7925    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7926            int fromIndex, int removedCount, int addedCount) {
7927        AccessibilityEvent event =
7928            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7929        event.setFromIndex(fromIndex);
7930        event.setRemovedCount(removedCount);
7931        event.setAddedCount(addedCount);
7932        event.setBeforeText(beforeText);
7933        sendAccessibilityEventUnchecked(event);
7934    }
7935
7936    @Override
7937    protected void onCreateContextMenu(ContextMenu menu) {
7938        super.onCreateContextMenu(menu);
7939        boolean added = false;
7940        mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
7941        // Problem with context menu on long press: the menu appears while the key in down and when
7942        // the key is released, the view does not receive the key_up event.
7943        // We need two layers of flags: mDPadCenterIsDown and mEnterKeyIsDown are set in key down/up
7944        // events. We cannot simply clear these flags in onTextContextMenuItem since
7945        // it may not be called (if the user/ discards the context menu with the back key).
7946        // We clear these flags here and mContextMenuTriggeredByKey saves that state so that it is
7947        // available in onTextContextMenuItem.
7948        mDPadCenterIsDown = mEnterKeyIsDown = false;
7949
7950        MenuHandler handler = new MenuHandler();
7951
7952        if (mText instanceof Spanned && hasSelectionController()) {
7953            long lastTouchOffset = getLastTouchOffsets();
7954            final int selStart = extractRangeStartFromLong(lastTouchOffset);
7955            final int selEnd = extractRangeEndFromLong(lastTouchOffset);
7956
7957            URLSpan[] urls = ((Spanned) mText).getSpans(selStart, selEnd, URLSpan.class);
7958            if (urls.length > 0) {
7959                menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
7960                        setOnMenuItemClickListener(handler);
7961
7962                added = true;
7963            }
7964        }
7965
7966        // The context menu is not empty, which will prevent the selection mode from starting.
7967        // Add a entry to start it in the context menu.
7968        // TODO Does not handle the case where a subclass does not call super.thisMethod or
7969        // populates the menu AFTER this call.
7970        if (menu.size() > 0) {
7971            menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode).
7972                    setOnMenuItemClickListener(handler);
7973            added = true;
7974        }
7975
7976        if (added) {
7977            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7978        }
7979    }
7980
7981    /**
7982     * Returns whether this text view is a current input method target.  The
7983     * default implementation just checks with {@link InputMethodManager}.
7984     */
7985    public boolean isInputMethodTarget() {
7986        InputMethodManager imm = InputMethodManager.peekInstance();
7987        return imm != null && imm.isActive(this);
7988    }
7989
7990    // Selection context mode
7991    private static final int ID_SELECT_ALL = android.R.id.selectAll;
7992    private static final int ID_CUT = android.R.id.cut;
7993    private static final int ID_COPY = android.R.id.copy;
7994    private static final int ID_PASTE = android.R.id.paste;
7995    // Context menu entries
7996    private static final int ID_COPY_URL = android.R.id.copyUrl;
7997    private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
7998
7999    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
8000        public boolean onMenuItemClick(MenuItem item) {
8001            return onTextContextMenuItem(item.getItemId());
8002        }
8003    }
8004
8005    /**
8006     * Called when a context menu option for the text view is selected.  Currently
8007     * this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode},
8008     * {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut}
8009     * or {@link android.R.id#copy}.
8010     *
8011     * @return true if the context menu item action was performed.
8012     */
8013    public boolean onTextContextMenuItem(int id) {
8014        int min = 0;
8015        int max = mText.length();
8016
8017        if (isFocused()) {
8018            final int selStart = getSelectionStart();
8019            final int selEnd = getSelectionEnd();
8020
8021            min = Math.max(0, Math.min(selStart, selEnd));
8022            max = Math.max(0, Math.max(selStart, selEnd));
8023        }
8024
8025        switch (id) {
8026            case ID_COPY_URL:
8027                URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
8028                if (urls.length >= 1) {
8029                    ClipData clip = null;
8030                    for (int i=0; i<urls.length; i++) {
8031                        Uri uri = Uri.parse(urls[0].getURL());
8032                        if (clip == null) {
8033                            clip = ClipData.newRawUri(null, uri);
8034                        } else {
8035                            clip.addItem(new ClipData.Item(uri));
8036                        }
8037                    }
8038                    if (clip != null) {
8039                        setPrimaryClip(clip);
8040                    }
8041                }
8042                stopSelectionActionMode();
8043                return true;
8044
8045            case ID_SELECTION_MODE:
8046                if (mSelectionActionMode != null) {
8047                    // Selection mode is already started, simply change selected part.
8048                    selectCurrentWord();
8049                } else {
8050                    startSelectionActionMode();
8051                }
8052                return true;
8053
8054            case ID_SELECT_ALL:
8055                // This does not enter text selection mode. Text is highlighted, so that it can be
8056                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
8057                selectAll();
8058                return true;
8059
8060            case ID_PASTE:
8061                paste(min, max);
8062                return true;
8063
8064            case ID_CUT:
8065                setPrimaryClip(ClipData.newPlainText(null, mTransformed.subSequence(min, max)));
8066                ((Editable) mText).delete(min, max);
8067                stopSelectionActionMode();
8068                return true;
8069
8070            case ID_COPY:
8071                setPrimaryClip(ClipData.newPlainText(null, mTransformed.subSequence(min, max)));
8072                stopSelectionActionMode();
8073                return true;
8074        }
8075        return false;
8076    }
8077
8078    /**
8079     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8080     * by [min, max] when replacing this region by paste.
8081     * Note that if there were two spaces (or more) at that position before, they are kept. We just
8082     * make sure we do not add an extra one from the paste content.
8083     */
8084    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8085        if (paste.length() > 0) {
8086            if (min > 0) {
8087                final char charBefore = mTransformed.charAt(min - 1);
8088                final char charAfter = paste.charAt(0);
8089
8090                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8091                    // Two spaces at beginning of paste: remove one
8092                    final int originalLength = mText.length();
8093                    ((Editable) mText).delete(min - 1, min);
8094                    // Due to filters, there is no guarantee that exactly one character was
8095                    // removed: count instead.
8096                    final int delta = mText.length() - originalLength;
8097                    min += delta;
8098                    max += delta;
8099                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8100                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8101                    // No space at beginning of paste: add one
8102                    final int originalLength = mText.length();
8103                    ((Editable) mText).replace(min, min, " ");
8104                    // Taking possible filters into account as above.
8105                    final int delta = mText.length() - originalLength;
8106                    min += delta;
8107                    max += delta;
8108                }
8109            }
8110
8111            if (max < mText.length()) {
8112                final char charBefore = paste.charAt(paste.length() - 1);
8113                final char charAfter = mTransformed.charAt(max);
8114
8115                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8116                    // Two spaces at end of paste: remove one
8117                    ((Editable) mText).delete(max, max + 1);
8118                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8119                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8120                    // No space at end of paste: add one
8121                    ((Editable) mText).replace(max, max, " ");
8122                }
8123            }
8124        }
8125
8126        return packRangeInLong(min, max);
8127    }
8128
8129    private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
8130        TextView shadowView = (TextView) inflate(mContext,
8131                com.android.internal.R.layout.text_drag_thumbnail, null);
8132
8133        if (shadowView == null) {
8134            throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
8135        }
8136
8137        if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
8138            text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
8139        }
8140        shadowView.setText(text);
8141        shadowView.setTextColor(getTextColors());
8142
8143        shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge);
8144        shadowView.setGravity(Gravity.CENTER);
8145
8146        shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
8147                ViewGroup.LayoutParams.WRAP_CONTENT));
8148
8149        final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8150        shadowView.measure(size, size);
8151
8152        shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight());
8153        shadowView.invalidate();
8154        return new DragShadowBuilder(shadowView);
8155    }
8156
8157    private static class DragLocalState {
8158        public TextView sourceTextView;
8159        public int start, end;
8160
8161        public DragLocalState(TextView sourceTextView, int start, int end) {
8162            this.sourceTextView = sourceTextView;
8163            this.start = start;
8164            this.end = end;
8165        }
8166    }
8167
8168    @Override
8169    public boolean performLongClick() {
8170        if (super.performLongClick()) {
8171            mDiscardNextActionUp = true;
8172            return true;
8173        }
8174
8175        boolean handled = false;
8176
8177        // Long press in empty space moves cursor and shows the Paste affordance if available.
8178        if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
8179                mInsertionControllerEnabled) {
8180            final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
8181            stopSelectionActionMode();
8182            Selection.setSelection((Spannable) mText, offset);
8183            getInsertionController().showWithPaste();
8184            handled = true;
8185        }
8186
8187        if (!handled && mSelectionActionMode != null) {
8188            if (touchPositionIsInSelection()) {
8189                // Start a drag
8190                final int start = getSelectionStart();
8191                final int end = getSelectionEnd();
8192                CharSequence selectedText = mTransformed.subSequence(start, end);
8193                ClipData data = ClipData.newPlainText(null, selectedText);
8194                DragLocalState localState = new DragLocalState(this, start, end);
8195                startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
8196                stopSelectionActionMode();
8197            } else {
8198                selectCurrentWord();
8199            }
8200            handled = true;
8201        }
8202
8203        // Start a new selection
8204        handled |= !handled && startSelectionActionMode();
8205
8206        if (handled) {
8207            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
8208            mDiscardNextActionUp = true;
8209        }
8210
8211        return handled;
8212    }
8213
8214    private boolean touchPositionIsInSelection() {
8215        int selectionStart = getSelectionStart();
8216        int selectionEnd = getSelectionEnd();
8217
8218        if (selectionStart == selectionEnd) {
8219            return false;
8220        }
8221
8222        if (selectionStart > selectionEnd) {
8223            int tmp = selectionStart;
8224            selectionStart = selectionEnd;
8225            selectionEnd = tmp;
8226            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8227        }
8228
8229        SelectionModifierCursorController selectionController = getSelectionController();
8230        int minOffset = selectionController.getMinTouchOffset();
8231        int maxOffset = selectionController.getMaxTouchOffset();
8232
8233        return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
8234    }
8235
8236    private static class SuggestionRangeSpan extends UnderlineSpan {
8237        // TODO themable, would be nice to make it a child class of TextAppearanceSpan, but
8238        // there is no way to have underline and TextAppearanceSpan.
8239    }
8240
8241    private class SuggestionsPopupWindow implements OnClickListener {
8242        private static final int MAX_NUMBER_SUGGESTIONS = 5;
8243        private static final int NO_SUGGESTIONS = -1;
8244        private final PopupWindow mContainer;
8245        private final ViewGroup[] mSuggestionViews = new ViewGroup[2];
8246        private final int[] mSuggestionViewLayouts = new int[] {
8247                mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout};
8248        private WordIterator mSuggestionWordIterator;
8249        private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan[0];
8250
8251        public SuggestionsPopupWindow() {
8252            mContainer = new PopupWindow(TextView.this.mContext, null,
8253                    com.android.internal.R.attr.textSuggestionsWindowStyle);
8254            mContainer.setSplitTouchEnabled(true);
8255            mContainer.setClippingEnabled(false);
8256            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
8257
8258            mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
8259            mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
8260        }
8261
8262        private class SuggestionInfo {
8263            int suggestionStart, suggestionEnd; // range of suggestion item with replacement text
8264            int spanStart, spanEnd; // range in TextView where text should be inserted
8265            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
8266            int suggestionIndex; // the index of the suggestion inside suggestionSpan
8267        }
8268
8269        private ViewGroup getViewGroup(boolean under) {
8270            final int viewIndex = under ? 0 : 1;
8271            ViewGroup viewGroup = mSuggestionViews[viewIndex];
8272
8273            if (viewGroup == null) {
8274                final int layout = mSuggestionViewLayouts[viewIndex];
8275                LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
8276                        getSystemService(Context.LAYOUT_INFLATER_SERVICE);
8277
8278                if (inflater == null) {
8279                    throw new IllegalArgumentException(
8280                            "Unable to create TextEdit suggestion window inflater");
8281                }
8282
8283                View view = inflater.inflate(layout, null);
8284
8285                if (! (view instanceof ViewGroup)) {
8286                    throw new IllegalArgumentException(
8287                            "Inflated TextEdit suggestion window is not a ViewGroup: " + view);
8288                }
8289
8290                viewGroup = (ViewGroup) view;
8291
8292                // Inflate the suggestion items once and for all.
8293                for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
8294                    View childView = inflater.inflate(mTextEditSuggestionItemLayout, viewGroup,
8295                            false);
8296
8297                    if (! (childView instanceof TextView)) {
8298                        throw new IllegalArgumentException(
8299                               "Inflated TextEdit suggestion item is not a TextView: " + childView);
8300                    }
8301
8302                    childView.setTag(new SuggestionInfo());
8303                    viewGroup.addView(childView);
8304                    childView.setOnClickListener(this);
8305                }
8306
8307                mSuggestionViews[viewIndex] = viewGroup;
8308            }
8309
8310            return viewGroup;
8311        }
8312
8313        public void show() {
8314            if (!(mText instanceof Editable)) return;
8315
8316            final int pos = TextView.this.getSelectionStart();
8317            Spannable spannable = (Spannable)TextView.this.mText;
8318            SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
8319            final int nbSpans = suggestionSpans.length;
8320
8321            ViewGroup viewGroup = getViewGroup(true);
8322            mContainer.setContentView(viewGroup);
8323
8324            int totalNbSuggestions = 0;
8325            int spanUnionStart = mText.length();
8326            int spanUnionEnd = 0;
8327
8328            for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
8329                SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
8330                final int spanStart = spannable.getSpanStart(suggestionSpan);
8331                final int spanEnd = spannable.getSpanEnd(suggestionSpan);
8332                spanUnionStart = Math.min(spanStart, spanUnionStart);
8333                spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
8334
8335                String[] suggestions = suggestionSpan.getSuggestions();
8336                int nbSuggestions = suggestions.length;
8337                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
8338                    TextView textView = (TextView) viewGroup.getChildAt(totalNbSuggestions);
8339                    textView.setText(suggestions[suggestionIndex]);
8340                    SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8341                    suggestionInfo.spanStart = spanStart;
8342                    suggestionInfo.spanEnd = spanEnd;
8343                    suggestionInfo.suggestionSpan = suggestionSpan;
8344                    suggestionInfo.suggestionIndex = suggestionIndex;
8345
8346                    totalNbSuggestions++;
8347                    if (totalNbSuggestions == MAX_NUMBER_SUGGESTIONS) {
8348                        // Also end outer for loop
8349                        spanIndex = nbSpans;
8350                        break;
8351                    }
8352                }
8353            }
8354
8355            if (totalNbSuggestions == 0) {
8356                // TODO Replace by final text, use a dedicated layout, add a fade out timer...
8357                TextView textView = (TextView) viewGroup.getChildAt(0);
8358                textView.setText("No suggestions available");
8359                SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8360                suggestionInfo.spanStart = NO_SUGGESTIONS;
8361                totalNbSuggestions++;
8362            } else {
8363                if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
8364                ((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
8365                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8366
8367                for (int i = 0; i < totalNbSuggestions; i++) {
8368                    final TextView textView = (TextView) viewGroup.getChildAt(i);
8369                    highlightTextDifferences(textView, spanUnionStart, spanUnionEnd);
8370                }
8371            }
8372
8373            for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
8374                viewGroup.getChildAt(i).setVisibility(i < totalNbSuggestions ? VISIBLE : GONE);
8375            }
8376
8377            final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8378            viewGroup.measure(size, size);
8379
8380            positionAtCursor();
8381        }
8382
8383        private long[] getWordLimits(CharSequence text) {
8384            // TODO locale for mSuggestionWordIterator
8385            if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator();
8386            mSuggestionWordIterator.setCharSequence(text);
8387
8388            // First pass will simply count the number of words to be able to create an array
8389            // Not too expensive since previous break positions are cached by the BreakIterator
8390            int nbWords = 0;
8391            int position = mSuggestionWordIterator.following(0);
8392            while (position != BreakIterator.DONE) {
8393                nbWords++;
8394                position = mSuggestionWordIterator.following(position);
8395            }
8396
8397            int index = 0;
8398            long[] result = new long[nbWords];
8399
8400            position = mSuggestionWordIterator.following(0);
8401            while (position != BreakIterator.DONE) {
8402                int wordStart = mSuggestionWordIterator.getBeginning(position);
8403                result[index++] = packRangeInLong(wordStart, position);
8404                position = mSuggestionWordIterator.following(position);
8405            }
8406
8407            return result;
8408        }
8409
8410        private TextAppearanceSpan highlightSpan(int index) {
8411            final int length = mHighlightSpans.length;
8412            if (index < length) {
8413                return mHighlightSpans[index];
8414            }
8415
8416            // Assumes indexes are requested in sequence: simply append one more item
8417            TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1];
8418            System.arraycopy(mHighlightSpans, 0, newArray, 0, length);
8419            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
8420                    android.R.style.TextAppearance_SuggestionHighlight);
8421            newArray[length] = highlightSpan;
8422            mHighlightSpans = newArray;
8423            return highlightSpan;
8424        }
8425
8426        private void highlightTextDifferences(TextView textView, int unionStart, int unionEnd) {
8427            SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8428            final int spanStart = suggestionInfo.spanStart;
8429            final int spanEnd = suggestionInfo.spanEnd;
8430
8431            // Remove all text formating by converting to Strings
8432            final String text = textView.getText().toString();
8433            final String sourceText = mText.subSequence(spanStart, spanEnd).toString();
8434
8435            long[] sourceWordLimits = getWordLimits(sourceText);
8436            long[] wordLimits = getWordLimits(text);
8437
8438            SpannableStringBuilder ssb = new SpannableStringBuilder();
8439            // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd]
8440            // The final result is made of 3 parts: the text before, between and after the span
8441            // This is the text before, provided for context
8442            ssb.append(mText.subSequence(unionStart, spanStart).toString());
8443
8444            // shift is used to offset spans positions wrt span's beginning
8445            final int shift = spanStart - unionStart;
8446            suggestionInfo.suggestionStart = shift;
8447            suggestionInfo.suggestionEnd = shift + text.length();
8448
8449            // This is the actual suggestion text, which will be highlighted by the following code
8450            ssb.append(text);
8451
8452            String[] words = new String[wordLimits.length];
8453            for (int i = 0; i < wordLimits.length; i++) {
8454                int wordStart = extractRangeStartFromLong(wordLimits[i]);
8455                int wordEnd = extractRangeEndFromLong(wordLimits[i]);
8456                words[i] = text.substring(wordStart, wordEnd);
8457            }
8458
8459            // Highlighted word algorithm is based on word matching between source and text
8460            // Matching words are found from left to right. TODO: change for RTL languages
8461            // Characters between matching words are highlighted
8462            int previousCommonWordIndex = -1;
8463            int nbHighlightSpans = 0;
8464            for (int i = 0; i < sourceWordLimits.length; i++) {
8465                int wordStart = extractRangeStartFromLong(sourceWordLimits[i]);
8466                int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]);
8467                String sourceWord = sourceText.substring(wordStart, wordEnd);
8468
8469                for (int j = previousCommonWordIndex + 1; j < words.length; j++) {
8470                    if (sourceWord.equals(words[j])) {
8471                        if (j != previousCommonWordIndex + 1) {
8472                            int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
8473                                extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
8474                            int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]);
8475                            ssb.setSpan(highlightSpan(nbHighlightSpans++),
8476                                    shift + firstDifferentPosition, shift + lastDifferentPosition,
8477                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8478                        } else {
8479                            // Compare characters between words
8480                            int previousSourceWordEnd = i == 0 ? 0 :
8481                                extractRangeEndFromLong(sourceWordLimits[i - 1]);
8482                            int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]);
8483                            String sourceSpaces = sourceText.substring(previousSourceWordEnd,
8484                                    sourceWordStart);
8485
8486                            int previousWordEnd = j == 0 ? 0 :
8487                                extractRangeEndFromLong(wordLimits[j - 1]);
8488                            int currentWordStart = extractRangeStartFromLong(wordLimits[j]);
8489                            String textSpaces = text.substring(previousWordEnd, currentWordStart);
8490
8491                            if (!sourceSpaces.equals(textSpaces)) {
8492                                ssb.setSpan(highlightSpan(nbHighlightSpans++),
8493                                        shift + previousWordEnd, shift + currentWordStart,
8494                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8495                            }
8496                        }
8497                        previousCommonWordIndex = j;
8498                        break;
8499                    }
8500                }
8501            }
8502
8503            // Finally, compare ends of Strings
8504            if (previousCommonWordIndex < words.length - 1) {
8505                int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
8506                    extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
8507                int lastDifferentPosition = textView.length();
8508                ssb.setSpan(highlightSpan(nbHighlightSpans++),
8509                        shift + firstDifferentPosition, shift + lastDifferentPosition,
8510                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8511            } else {
8512                int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 :
8513                    extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]);
8514                String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length());
8515
8516                int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 :
8517                    extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
8518                String textSpaces = text.substring(lastCommonTextWordEnd, textView.length());
8519
8520                if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) {
8521                    ssb.setSpan(highlightSpan(nbHighlightSpans++),
8522                            shift + lastCommonTextWordEnd, shift + textView.length(),
8523                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8524                }
8525            }
8526
8527            // Final part, text after the current suggestion range.
8528            ssb.append(mText.subSequence(spanEnd, unionEnd).toString());
8529            textView.setText(ssb);
8530        }
8531
8532        public void hide() {
8533            if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
8534                ((Editable) mText).removeSpan(mSuggestionRangeSpan);
8535            }
8536            mContainer.dismiss();
8537        }
8538
8539        @Override
8540        public void onClick(View view) {
8541            if (view instanceof TextView) {
8542                TextView textView = (TextView) view;
8543                SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8544                final int spanStart = suggestionInfo.spanStart;
8545                final int spanEnd = suggestionInfo.spanEnd;
8546                if (spanStart != NO_SUGGESTIONS) {
8547                    // SuggestionSpans are removed by replace: save them before
8548                    Editable editable = ((Editable) mText);
8549                    SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
8550                            SuggestionSpan.class);
8551                    final int length = suggestionSpans.length;
8552                    int[] suggestionSpansStarts = new int[length];
8553                    int[] suggestionSpansEnds = new int[length];
8554                    int[] suggestionSpansFlags = new int[length];
8555                    for (int i = 0; i < length; i++) {
8556                        final SuggestionSpan suggestionSpan = suggestionSpans[i];
8557                        suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
8558                        suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
8559                        suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
8560                    }
8561
8562                    final int suggestionStart = suggestionInfo.suggestionStart;
8563                    final int suggestionEnd = suggestionInfo.suggestionEnd;
8564                    final String suggestion = textView.getText().subSequence(
8565                            suggestionStart, suggestionEnd).toString();
8566                    final String originalText = mText.subSequence(spanStart, spanEnd).toString();
8567                    ((Editable) mText).replace(spanStart, spanEnd, suggestion);
8568
8569                    // Swap text content between actual text and Suggestion span
8570                    String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
8571                    suggestions[suggestionInfo.suggestionIndex] = originalText;
8572
8573                    // Notify source IME of the suggestion pick
8574                    if (!TextUtils.isEmpty(
8575                            suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
8576                        InputMethodManager imm = InputMethodManager.peekInstance();
8577                        imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
8578                                suggestionInfo.suggestionIndex);
8579                    }
8580
8581                    // Restore previous SuggestionSpans
8582                    final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
8583                    for (int i = 0; i < length; i++) {
8584                        // Only spans that include the modified region make sense after replacement
8585                        // Spans partially included in the replaced region are removed, there is no
8586                        // way to assign them a valid range after replacement
8587                        if (suggestionSpansStarts[i] <= spanStart &&
8588                                suggestionSpansEnds[i] >= spanEnd) {
8589                            editable.setSpan(suggestionSpans[i], suggestionSpansStarts[i],
8590                                    suggestionSpansEnds[i] + lengthDifference,
8591                                    suggestionSpansFlags[i]);
8592                        }
8593                    }
8594                }
8595            }
8596            hide();
8597        }
8598
8599        void positionAtCursor() {
8600            View contentView = mContainer.getContentView();
8601            int width = contentView.getMeasuredWidth();
8602            int height = contentView.getMeasuredHeight();
8603            final int offset = TextView.this.getSelectionStart();
8604            final int line = mLayout.getLineForOffset(offset);
8605            final int lineBottom = mLayout.getLineBottom(line);
8606            float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
8607
8608            final Rect bounds = sCursorControllerTempRect;
8609            bounds.left = (int) (primaryHorizontal - width / 2.0f);
8610            bounds.top = lineBottom;
8611
8612            bounds.right = bounds.left + width;
8613            bounds.bottom = bounds.top + height;
8614
8615            convertFromViewportToContentCoordinates(bounds);
8616
8617            final int[] coords = mTempCoords;
8618            TextView.this.getLocationInWindow(coords);
8619            coords[0] += bounds.left;
8620            coords[1] += bounds.top;
8621
8622            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
8623            final int screenHeight = displayMetrics.heightPixels;
8624
8625            // Vertical clipping
8626            if (coords[1] + height > screenHeight) {
8627                // Try to position above current line instead
8628                // TODO use top layout instead, reverse suggestion order,
8629                // try full screen vertical down if it still does not fit. TBD with designers.
8630
8631                // Update dimensions from new view
8632                contentView = mContainer.getContentView();
8633                width = contentView.getMeasuredWidth();
8634                height = contentView.getMeasuredHeight();
8635
8636                final int lineTop = mLayout.getLineTop(line);
8637                final int lineHeight = lineBottom - lineTop;
8638                coords[1] -= height + lineHeight;
8639            }
8640
8641            // Horizontal clipping
8642            coords[0] = Math.max(0, coords[0]);
8643            coords[0] = Math.min(displayMetrics.widthPixels - width, coords[0]);
8644
8645            mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
8646        }
8647    }
8648
8649    void showSuggestions() {
8650        if (!mSuggestionsEnabled || !isTextEditable()) return;
8651
8652        if (mSuggestionsPopupWindow == null) {
8653            mSuggestionsPopupWindow = new SuggestionsPopupWindow();
8654        }
8655        hideControllers();
8656        mSuggestionsPopupWindow.show();
8657    }
8658
8659    void hideSuggestions() {
8660        if (mSuggestionsPopupWindow != null) {
8661            mSuggestionsPopupWindow.hide();
8662        }
8663    }
8664
8665    /**
8666     * Some parts of the text can have alternate suggestion text attached. This is typically done by
8667     * the IME by adding {@link SuggestionSpan}s to the text.
8668     *
8669     * When suggestions are enabled (default), this list of suggestions will be displayed when the
8670     * user double taps on these parts of the text. No suggestions are displayed when this value is
8671     * false. Use {@link #setSuggestionsEnabled(boolean)} to change this value.
8672     *
8673     * @return true if the suggestions popup window is enabled.
8674     *
8675     * @attr ref android.R.styleable#TextView_suggestionsEnabled
8676     */
8677    public boolean isSuggestionsEnabled() {
8678        return mSuggestionsEnabled;
8679    }
8680
8681    /**
8682     * Enables or disables the suggestion popup. See {@link #isSuggestionsEnabled()}.
8683     *
8684     * @param enabled Whether or not suggestions are enabled.
8685     */
8686    public void setSuggestionsEnabled(boolean enabled) {
8687        mSuggestionsEnabled = enabled;
8688    }
8689
8690    /**
8691     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8692     * selection is initiated in this View.
8693     *
8694     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8695     * Paste actions, depending on what this View supports.
8696     *
8697     * A custom implementation can add new entries in the default menu in its
8698     * {@link ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The default actions
8699     * can also be removed from the menu using {@link Menu#removeItem(int)} and passing
8700     * {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} or
8701     * {@link android.R.id#paste} ids as parameters.
8702     *
8703     * Returning false from {@link ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will
8704     * prevent the action mode from being started.
8705     *
8706     * Action click events should be handled by the custom implementation of
8707     * {@link ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8708     *
8709     * Note that text selection mode is not started when a TextView receives focus and the
8710     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8711     * that case, to allow for quick replacement.
8712     */
8713    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8714        mCustomSelectionActionModeCallback = actionModeCallback;
8715    }
8716
8717    /**
8718     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8719     *
8720     * @return The current custom selection callback.
8721     */
8722    public ActionMode.Callback getCustomSelectionActionModeCallback() {
8723        return mCustomSelectionActionModeCallback;
8724    }
8725
8726    /**
8727     *
8728     * @return true if the selection mode was actually started.
8729     */
8730    private boolean startSelectionActionMode() {
8731        if (mSelectionActionMode != null) {
8732            // Selection action mode is already started
8733            return false;
8734        }
8735
8736        if (!canSelectText() || !requestFocus()) {
8737            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
8738            return false;
8739        }
8740
8741        if (!hasSelection()) {
8742            // There may already be a selection on device rotation
8743            boolean currentWordSelected = selectCurrentWord();
8744            if (!currentWordSelected) {
8745                // No word found under cursor or text selection not permitted.
8746                return false;
8747            }
8748        }
8749
8750        ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
8751        mSelectionActionMode = startActionMode(actionModeCallback);
8752        final boolean selectionStarted = mSelectionActionMode != null;
8753
8754        if (selectionStarted && !mTextIsSelectable) {
8755            // Show the IME to be able to replace text, except when selecting non editable text.
8756            final InputMethodManager imm = InputMethodManager.peekInstance();
8757            if (imm != null) imm.showSoftInput(this, 0, null);
8758        }
8759
8760        return selectionStarted;
8761    }
8762
8763    private void stopSelectionActionMode() {
8764        if (mSelectionActionMode != null) {
8765            // This will hide the mSelectionModifierCursorController
8766            mSelectionActionMode.finish();
8767        }
8768    }
8769
8770    /**
8771     * Paste clipboard content between min and max positions.
8772     */
8773    private void paste(int min, int max) {
8774        ClipboardManager clipboard =
8775            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8776        ClipData clip = clipboard.getPrimaryClip();
8777        if (clip != null) {
8778            boolean didFirst = false;
8779            for (int i=0; i<clip.getItemCount(); i++) {
8780                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
8781                if (paste != null) {
8782                    if (!didFirst) {
8783                        long minMax = prepareSpacesAroundPaste(min, max, paste);
8784                        min = extractRangeStartFromLong(minMax);
8785                        max = extractRangeEndFromLong(minMax);
8786                        Selection.setSelection((Spannable) mText, max);
8787                        ((Editable) mText).replace(min, max, paste);
8788                        didFirst = true;
8789                    } else {
8790                        ((Editable) mText).insert(getSelectionEnd(), "\n");
8791                        ((Editable) mText).insert(getSelectionEnd(), paste);
8792                    }
8793                }
8794            }
8795            stopSelectionActionMode();
8796            sLastCutOrCopyTime = 0;
8797        }
8798    }
8799
8800    private void setPrimaryClip(ClipData clip) {
8801        ClipboardManager clipboard = (ClipboardManager) getContext().
8802                getSystemService(Context.CLIPBOARD_SERVICE);
8803        clipboard.setPrimaryClip(clip);
8804        sLastCutOrCopyTime = SystemClock.uptimeMillis();
8805    }
8806
8807    /**
8808     * An ActionMode Callback class that is used to provide actions while in text selection mode.
8809     *
8810     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
8811     * on which of these this TextView supports.
8812     */
8813    private class SelectionActionModeCallback implements ActionMode.Callback {
8814
8815        @Override
8816        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
8817            TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
8818
8819            boolean allowText = getContext().getResources().getBoolean(
8820                    com.android.internal.R.bool.allow_action_menu_item_text_with_icon);
8821
8822            mode.setTitle(allowText ?
8823                    mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
8824            mode.setSubtitle(null);
8825
8826            int selectAllIconId = 0; // No icon by default
8827            if (!allowText) {
8828                // Provide an icon, text will not be displayed on smaller screens.
8829                selectAllIconId = styledAttributes.getResourceId(
8830                        R.styleable.Theme_actionModeSelectAllDrawable, 0);
8831            }
8832
8833            menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
8834                    setIcon(selectAllIconId).
8835                    setAlphabeticShortcut('a').
8836                    setShowAsAction(
8837                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
8838
8839            if (canCut()) {
8840                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
8841                    setIcon(styledAttributes.getResourceId(
8842                            R.styleable.Theme_actionModeCutDrawable, 0)).
8843                    setAlphabeticShortcut('x').
8844                    setShowAsAction(
8845                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
8846            }
8847
8848            if (canCopy()) {
8849                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
8850                    setIcon(styledAttributes.getResourceId(
8851                            R.styleable.Theme_actionModeCopyDrawable, 0)).
8852                    setAlphabeticShortcut('c').
8853                    setShowAsAction(
8854                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
8855            }
8856
8857            if (canPaste()) {
8858                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
8859                        setIcon(styledAttributes.getResourceId(
8860                                R.styleable.Theme_actionModePasteDrawable, 0)).
8861                        setAlphabeticShortcut('v').
8862                        setShowAsAction(
8863                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
8864            }
8865
8866            styledAttributes.recycle();
8867
8868            if (mCustomSelectionActionModeCallback != null) {
8869                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
8870                    // The custom mode can choose to cancel the action mode
8871                    return false;
8872                }
8873            }
8874
8875            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
8876                getSelectionController().show();
8877                return true;
8878            } else {
8879                return false;
8880            }
8881        }
8882
8883        @Override
8884        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
8885            if (mCustomSelectionActionModeCallback != null) {
8886                return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
8887            }
8888            return true;
8889        }
8890
8891        @Override
8892        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
8893            if (mCustomSelectionActionModeCallback != null &&
8894                 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
8895                return true;
8896            }
8897            return onTextContextMenuItem(item.getItemId());
8898        }
8899
8900        @Override
8901        public void onDestroyActionMode(ActionMode mode) {
8902            if (mCustomSelectionActionModeCallback != null) {
8903                mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
8904            }
8905            Selection.setSelection((Spannable) mText, getSelectionEnd());
8906
8907            if (mSelectionModifierCursorController != null) {
8908                mSelectionModifierCursorController.hide();
8909            }
8910
8911            mSelectionActionMode = null;
8912        }
8913    }
8914
8915    private class PastePopupWindow implements OnClickListener {
8916        private final PopupWindow mContainer;
8917        private final View[] mPasteViews = new View[4];
8918        private final int[] mPasteViewLayouts = new int[] {
8919                mTextEditPasteWindowLayout,  mTextEditNoPasteWindowLayout,
8920                mTextEditSidePasteWindowLayout, mTextEditSideNoPasteWindowLayout };
8921
8922        public PastePopupWindow() {
8923            mContainer = new PopupWindow(TextView.this.mContext, null,
8924                    com.android.internal.R.attr.textSelectHandleWindowStyle);
8925            mContainer.setSplitTouchEnabled(true);
8926            mContainer.setClippingEnabled(false);
8927            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
8928
8929            mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
8930            mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
8931        }
8932
8933        private int viewIndex(boolean onTop) {
8934            return (onTop ? 0 : 1<<1) + (canPaste() ? 0 : 1<<0);
8935        }
8936
8937        private void updateContent(boolean onTop) {
8938            final int viewIndex = viewIndex(onTop);
8939            View view = mPasteViews[viewIndex];
8940
8941            if (view == null) {
8942                final int layout = mPasteViewLayouts[viewIndex];
8943                LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
8944                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
8945                if (inflater != null) {
8946                    view = inflater.inflate(layout, null);
8947                }
8948
8949                if (view == null) {
8950                    throw new IllegalArgumentException("Unable to inflate TextEdit paste window");
8951                }
8952
8953                final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8954                view.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
8955                        ViewGroup.LayoutParams.WRAP_CONTENT));
8956                view.measure(size, size);
8957
8958                view.setOnClickListener(this);
8959
8960                mPasteViews[viewIndex] = view;
8961            }
8962
8963            mContainer.setContentView(view);
8964        }
8965
8966        public void show() {
8967            updateContent(true);
8968            positionAtCursor();
8969        }
8970
8971        public void hide() {
8972            mContainer.dismiss();
8973        }
8974
8975        public boolean isShowing() {
8976            return mContainer.isShowing();
8977        }
8978
8979        @Override
8980        public void onClick(View v) {
8981            if (canPaste()) {
8982                paste(getSelectionStart(), getSelectionEnd());
8983            }
8984            hide();
8985        }
8986
8987        void positionAtCursor() {
8988            View contentView = mContainer.getContentView();
8989            int width = contentView.getMeasuredWidth();
8990            int height = contentView.getMeasuredHeight();
8991            final int offset = TextView.this.getSelectionStart();
8992            final int line = mLayout.getLineForOffset(offset);
8993            final int lineTop = mLayout.getLineTop(line);
8994            float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
8995
8996            final Rect bounds = sCursorControllerTempRect;
8997            bounds.left = (int) (primaryHorizontal - width / 2.0f);
8998            bounds.top = lineTop - height;
8999
9000            bounds.right = bounds.left + width;
9001            bounds.bottom = bounds.top + height;
9002
9003            convertFromViewportToContentCoordinates(bounds);
9004
9005            final int[] coords = mTempCoords;
9006            TextView.this.getLocationInWindow(coords);
9007            coords[0] += bounds.left;
9008            coords[1] += bounds.top;
9009
9010            final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
9011            if (coords[1] < 0) {
9012                updateContent(false);
9013                // Update dimensions from new view
9014                contentView = mContainer.getContentView();
9015                width = contentView.getMeasuredWidth();
9016                height = contentView.getMeasuredHeight();
9017
9018                // Vertical clipping, move under edited line and to the side of insertion cursor
9019                // TODO bottom clipping in case there is no system bar
9020                coords[1] += height;
9021                final int lineBottom = mLayout.getLineBottom(line);
9022                final int lineHeight = lineBottom - lineTop;
9023                coords[1] += lineHeight;
9024
9025                // Move to right hand side of insertion cursor by default. TODO RTL text.
9026                final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
9027                final int handleHalfWidth = handle.getIntrinsicWidth() / 2;
9028
9029                if (primaryHorizontal + handleHalfWidth + width < screenWidth) {
9030                    coords[0] += handleHalfWidth + width / 2;
9031                } else {
9032                    coords[0] -= handleHalfWidth + width / 2;
9033                }
9034            } else {
9035                // Horizontal clipping
9036                coords[0] = Math.max(0, coords[0]);
9037                coords[0] = Math.min(screenWidth - width, coords[0]);
9038            }
9039
9040            mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
9041        }
9042    }
9043
9044    private abstract class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
9045        protected Drawable mDrawable;
9046        private final PopupWindow mContainer;
9047        // Position with respect to the parent TextView
9048        private int mPositionX, mPositionY;
9049        private boolean mIsDragging;
9050        // Offset from touch position to mPosition
9051        private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
9052        protected float mHotspotX;
9053        // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
9054        private float mTouchOffsetY;
9055        // Where the touch position should be on the handle to ensure a maximum cursor visibility
9056        private float mIdealVerticalOffset;
9057        // Parent's (TextView) previous position in window
9058        private int mLastParentX, mLastParentY;
9059        // PopupWindow container absolute position with respect to the enclosing window
9060        private int mContainerPositionX, mContainerPositionY;
9061        // Visible or not (scrolled off screen), whether or not this handle should be visible
9062        private boolean mIsActive = false;
9063        // Used to detect that setFrame was called
9064        private boolean mNeedsUpdate = true;
9065
9066        public HandleView() {
9067            super(TextView.this.mContext);
9068            mContainer = new PopupWindow(TextView.this.mContext, null,
9069                    com.android.internal.R.attr.textSelectHandleWindowStyle);
9070            mContainer.setSplitTouchEnabled(true);
9071            mContainer.setClippingEnabled(false);
9072            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
9073            mContainer.setContentView(this);
9074
9075            initDrawable();
9076
9077            final int handleHeight = mDrawable.getIntrinsicHeight();
9078            mTouchOffsetY = -0.3f * handleHeight;
9079            mIdealVerticalOffset = 0.7f * handleHeight;
9080        }
9081
9082        @Override
9083        protected boolean setFrame(int left, int top, int right, int bottom) {
9084            boolean changed = super.setFrame(left, top, right, bottom);
9085            // onPreDraw is called for PhoneWindow before the layout of this view is
9086            // performed. Make sure to update position, even if container didn't move.
9087            if (changed) mNeedsUpdate  = true;
9088            return changed;
9089        }
9090
9091        protected abstract void initDrawable();
9092
9093        // Touch-up filter: number of previous positions remembered
9094        private static final int HISTORY_SIZE = 5;
9095        private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
9096        private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
9097        private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
9098        private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
9099        private int mPreviousOffsetIndex = 0;
9100        private int mNumberPreviousOffsets = 0;
9101
9102        private void startTouchUpFilter(int offset) {
9103            mNumberPreviousOffsets = 0;
9104            addPositionToTouchUpFilter(offset);
9105        }
9106
9107        private void addPositionToTouchUpFilter(int offset) {
9108            if (mNumberPreviousOffsets > 0 &&
9109                    mPreviousOffsets[mPreviousOffsetIndex] == offset) {
9110                // Make sure only actual changes of position are recorded.
9111                return;
9112            }
9113
9114            mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
9115            mPreviousOffsets[mPreviousOffsetIndex] = offset;
9116            mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
9117            mNumberPreviousOffsets++;
9118        }
9119
9120        private void filterOnTouchUp() {
9121            final long now = SystemClock.uptimeMillis();
9122            int i = 0;
9123            int index = mPreviousOffsetIndex;
9124            final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
9125            while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
9126                i++;
9127                index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
9128            }
9129
9130            if (i > 0 && i < iMax &&
9131                    (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
9132                updateOffset(mPreviousOffsets[index]);
9133            }
9134        }
9135
9136        @Override
9137        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
9138            setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
9139        }
9140
9141        public void show() {
9142            if (isShowing()) {
9143                mContainer.update(mContainerPositionX, mContainerPositionY,
9144                        mRight - mLeft, mBottom - mTop);
9145            } else {
9146                mContainer.showAtLocation(TextView.this, 0,
9147                        mContainerPositionX, mContainerPositionY);
9148
9149                mIsActive = true;
9150
9151                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9152                vto.addOnPreDrawListener(this);
9153            }
9154        }
9155
9156        protected void dismiss() {
9157            mIsDragging = false;
9158            mContainer.dismiss();
9159        }
9160
9161        public void hide() {
9162            dismiss();
9163
9164            mIsActive = false;
9165
9166            ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9167            vto.removeOnPreDrawListener(this);
9168        }
9169
9170        public boolean isShowing() {
9171            return mContainer.isShowing();
9172        }
9173
9174        private boolean isPositionVisible() {
9175            // Always show a dragging handle.
9176            if (mIsDragging) {
9177                return true;
9178            }
9179
9180            if (isInBatchEditMode()) {
9181                return false;
9182            }
9183
9184            final int extendedPaddingTop = getExtendedPaddingTop();
9185            final int extendedPaddingBottom = getExtendedPaddingBottom();
9186            final int compoundPaddingLeft = getCompoundPaddingLeft();
9187            final int compoundPaddingRight = getCompoundPaddingRight();
9188
9189            final TextView textView = TextView.this;
9190
9191            if (mTempRect == null) mTempRect = new Rect();
9192            final Rect clip = mTempRect;
9193            clip.left = compoundPaddingLeft;
9194            clip.top = extendedPaddingTop;
9195            clip.right = textView.getWidth() - compoundPaddingRight;
9196            clip.bottom = textView.getHeight() - extendedPaddingBottom;
9197
9198            final ViewParent parent = textView.getParent();
9199            if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
9200                return false;
9201            }
9202
9203            final int[] coords = mTempCoords;
9204            textView.getLocationInWindow(coords);
9205            final int posX = coords[0] + mPositionX + (int) mHotspotX;
9206            final int posY = coords[1] + mPositionY;
9207
9208            // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
9209            return posX >= clip.left - 1 && posX <= clip.right + 1 &&
9210                    posY >= clip.top && posY <= clip.bottom;
9211        }
9212
9213        public abstract int getCurrentCursorOffset();
9214
9215        public abstract void updateOffset(int offset);
9216
9217        public abstract void updatePosition(float x, float y);
9218
9219        protected void positionAtCursorOffset(int offset) {
9220            // A HandleView relies on the layout, which may be nulled by external methods.
9221            if (mLayout == null) {
9222                // Will update controllers' state, hiding them and stopping selection mode if needed
9223                prepareCursorControllers();
9224                return;
9225            }
9226
9227            addPositionToTouchUpFilter(offset);
9228            final int line = mLayout.getLineForOffset(offset);
9229            final int lineBottom = mLayout.getLineBottom(line);
9230
9231            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
9232            mPositionY = lineBottom;
9233
9234            // Take TextView's padding into account.
9235            mPositionX += viewportToContentHorizontalOffset();
9236            mPositionY += viewportToContentVerticalOffset();
9237        }
9238
9239        private void checkForContainerPositionChange() {
9240            positionAtCursorOffset(getCurrentCursorOffset());
9241
9242            final int previousContainerPositionX = mContainerPositionX;
9243            final int previousContainerPositionY = mContainerPositionY;
9244
9245            TextView.this.getLocationInWindow(mTempCoords);
9246            mContainerPositionX = mTempCoords[0] + mPositionX;
9247            mContainerPositionY = mTempCoords[1] + mPositionY;
9248
9249            mNeedsUpdate |= previousContainerPositionX != mContainerPositionX;
9250            mNeedsUpdate |= previousContainerPositionY != mContainerPositionY;
9251        }
9252
9253        public boolean onPreDraw() {
9254            checkForContainerPositionChange();
9255            if (mNeedsUpdate) {
9256                if (mIsDragging) {
9257                    if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
9258                        mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
9259                        mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
9260                        mLastParentX = mTempCoords[0];
9261                        mLastParentY = mTempCoords[1];
9262                    }
9263                }
9264
9265                onHandleMoved();
9266
9267                if (isPositionVisible()) {
9268                    mContainer.update(mContainerPositionX, mContainerPositionY,
9269                            mRight - mLeft, mBottom - mTop);
9270
9271                    if (mIsActive && !isShowing()) {
9272                        show();
9273                    }
9274                } else {
9275                    if (isShowing()) {
9276                        dismiss();
9277                    }
9278                }
9279                mNeedsUpdate = false;
9280            }
9281            return true;
9282        }
9283
9284        @Override
9285        protected void onDraw(Canvas c) {
9286            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
9287            mDrawable.draw(c);
9288        }
9289
9290        @Override
9291        public boolean onTouchEvent(MotionEvent ev) {
9292            switch (ev.getActionMasked()) {
9293                case MotionEvent.ACTION_DOWN: {
9294                    startTouchUpFilter(getCurrentCursorOffset());
9295                    mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
9296                    mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
9297
9298                    final int[] coords = mTempCoords;
9299                    TextView.this.getLocationInWindow(coords);
9300                    mLastParentX = coords[0];
9301                    mLastParentY = coords[1];
9302                    mIsDragging = true;
9303                    break;
9304                }
9305
9306                case MotionEvent.ACTION_MOVE: {
9307                    final float rawX = ev.getRawX();
9308                    final float rawY = ev.getRawY();
9309
9310                    // Vertical hysteresis: vertical down movement tends to snap to ideal offset
9311                    final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
9312                    final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
9313                    float newVerticalOffset;
9314                    if (previousVerticalOffset < mIdealVerticalOffset) {
9315                        newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
9316                        newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
9317                    } else {
9318                        newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
9319                        newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
9320                    }
9321                    mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
9322
9323                    final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
9324                    final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
9325
9326                    updatePosition(newPosX, newPosY);
9327                    break;
9328                }
9329
9330                case MotionEvent.ACTION_UP:
9331                    filterOnTouchUp();
9332                    mIsDragging = false;
9333                    break;
9334
9335                case MotionEvent.ACTION_CANCEL:
9336                    mIsDragging = false;
9337                    break;
9338            }
9339            return true;
9340        }
9341
9342        public boolean isDragging() {
9343            return mIsDragging;
9344        }
9345
9346        void onHandleMoved() {
9347            // Does nothing by default
9348        }
9349
9350        public void onDetached() {
9351            // Should be overriden to clean possible Runnable
9352        }
9353    }
9354
9355    private class InsertionHandleView extends HandleView {
9356        private static final int DELAY_BEFORE_FADE_OUT = 4000;
9357        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
9358
9359        // Used to detect taps on the insertion handle, which will affect the PastePopupWindow
9360        private float mDownPositionX, mDownPositionY;
9361        private PastePopupWindow mPastePopupWindow;
9362        private Runnable mHider;
9363        private Runnable mPastePopupShower;
9364
9365        @Override
9366        public void show() {
9367            super.show();
9368            hideDelayed();
9369            hidePastePopupWindow();
9370        }
9371
9372        public void show(int delayBeforePaste) {
9373            show();
9374
9375            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
9376            if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
9377                delayBeforePaste = 0;
9378            }
9379            if (delayBeforePaste == 0 || canPaste()) {
9380                if (mPastePopupShower == null) {
9381                    mPastePopupShower = new Runnable() {
9382                        public void run() {
9383                            showPastePopupWindow();
9384                        }
9385                    };
9386                }
9387                TextView.this.postDelayed(mPastePopupShower, delayBeforePaste);
9388            }
9389        }
9390
9391        @Override
9392        protected void dismiss() {
9393            super.dismiss();
9394            onDetached();
9395        }
9396
9397        private void hideDelayed() {
9398            removeHiderCallback();
9399            if (mHider == null) {
9400                mHider = new Runnable() {
9401                    public void run() {
9402                        hide();
9403                    }
9404                };
9405            }
9406            TextView.this.postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
9407        }
9408
9409        private void removeHiderCallback() {
9410            if (mHider != null) {
9411                TextView.this.removeCallbacks(mHider);
9412            }
9413        }
9414
9415        @Override
9416        protected void initDrawable() {
9417            if (mSelectHandleCenter == null) {
9418                mSelectHandleCenter = mContext.getResources().getDrawable(
9419                        mTextSelectHandleRes);
9420            }
9421            mDrawable = mSelectHandleCenter;
9422            mHotspotX = mDrawable.getIntrinsicWidth() / 2.0f;
9423        }
9424
9425        @Override
9426        public boolean onTouchEvent(MotionEvent ev) {
9427            final boolean result = super.onTouchEvent(ev);
9428
9429            switch (ev.getActionMasked()) {
9430                case MotionEvent.ACTION_DOWN:
9431                    mDownPositionX = ev.getRawX();
9432                    mDownPositionY = ev.getRawY();
9433                    break;
9434
9435                case MotionEvent.ACTION_UP:
9436                    final float deltaX = mDownPositionX - ev.getRawX();
9437                    final float deltaY = mDownPositionY - ev.getRawY();
9438                    final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
9439                    if (distanceSquared < mSquaredTouchSlopDistance) {
9440                        if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
9441                            // Tapping on the handle dismisses the displayed paste view,
9442                            mPastePopupWindow.hide();
9443                        } else {
9444                            show(0);
9445                        }
9446                    }
9447                    hideDelayed();
9448                    break;
9449
9450                case MotionEvent.ACTION_CANCEL:
9451                    hideDelayed();
9452                    break;
9453
9454                default:
9455                    break;
9456            }
9457
9458            return result;
9459        }
9460
9461        @Override
9462        public int getCurrentCursorOffset() {
9463            return TextView.this.getSelectionStart();
9464        }
9465
9466        @Override
9467        public void updateOffset(int offset) {
9468            Selection.setSelection((Spannable) mText, offset);
9469        }
9470
9471        @Override
9472        public void updatePosition(float x, float y) {
9473            updateOffset(getOffsetForPosition(x, y));
9474        }
9475
9476        void showPastePopupWindow() {
9477            if (mPastePopupWindow == null) {
9478                mPastePopupWindow = new PastePopupWindow();
9479            }
9480            mPastePopupWindow.show();
9481        }
9482
9483        @Override
9484        void onHandleMoved() {
9485            removeHiderCallback();
9486            hidePastePopupWindow();
9487        }
9488
9489        void hidePastePopupWindow() {
9490            if (mPastePopupShower != null) {
9491                TextView.this.removeCallbacks(mPastePopupShower);
9492            }
9493            if (mPastePopupWindow != null) {
9494                mPastePopupWindow.hide();
9495            }
9496        }
9497
9498        @Override
9499        public void onDetached() {
9500            removeHiderCallback();
9501            hidePastePopupWindow();
9502        }
9503    }
9504
9505    private class SelectionStartHandleView extends HandleView {
9506        @Override
9507        protected void initDrawable() {
9508            if (mSelectHandleLeft == null) {
9509                mSelectHandleLeft = mContext.getResources().getDrawable(
9510                        mTextSelectHandleLeftRes);
9511            }
9512            mDrawable = mSelectHandleLeft;
9513            mHotspotX = mDrawable.getIntrinsicWidth() * 3.0f / 4.0f;
9514        }
9515
9516        @Override
9517        public int getCurrentCursorOffset() {
9518            return TextView.this.getSelectionStart();
9519        }
9520
9521        @Override
9522        public void updateOffset(int offset) {
9523            Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
9524        }
9525
9526        @Override
9527        public void updatePosition(float x, float y) {
9528            final int selectionStart = getSelectionStart();
9529            final int selectionEnd = getSelectionEnd();
9530
9531            int offset = getOffsetForPosition(x, y);
9532
9533            // No need to redraw when the offset is unchanged
9534            if (offset == selectionStart) return;
9535            // Handles can not cross and selection is at least one character
9536            if (offset >= selectionEnd) offset = selectionEnd - 1;
9537
9538            Selection.setSelection((Spannable) mText, offset, selectionEnd);
9539        }
9540    }
9541
9542    private class SelectionEndHandleView extends HandleView {
9543        @Override
9544        protected void initDrawable() {
9545            if (mSelectHandleRight == null) {
9546                mSelectHandleRight = mContext.getResources().getDrawable(
9547                        mTextSelectHandleRightRes);
9548            }
9549            mDrawable = mSelectHandleRight;
9550            mHotspotX = mDrawable.getIntrinsicWidth() / 4.0f;
9551        }
9552
9553        @Override
9554        public int getCurrentCursorOffset() {
9555            return TextView.this.getSelectionEnd();
9556        }
9557
9558        @Override
9559        public void updateOffset(int offset) {
9560            Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
9561        }
9562
9563        @Override
9564        public void updatePosition(float x, float y) {
9565            final int selectionStart = getSelectionStart();
9566            final int selectionEnd = getSelectionEnd();
9567
9568            int offset = getOffsetForPosition(x, y);
9569
9570            // No need to redraw when the offset is unchanged
9571            if (offset == selectionEnd) return;
9572            // Handles can not cross and selection is at least one character
9573            if (offset <= selectionStart) offset = selectionStart + 1;
9574
9575            Selection.setSelection((Spannable) mText, selectionStart, offset);
9576        }
9577    }
9578
9579    /**
9580     * A CursorController instance can be used to control a cursor in the text.
9581     * It is not used outside of {@link TextView}.
9582     * @hide
9583     */
9584    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
9585        /**
9586         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
9587         * See also {@link #hide()}.
9588         */
9589        public void show();
9590
9591        /**
9592         * Hide the cursor controller from screen.
9593         * See also {@link #show()}.
9594         */
9595        public void hide();
9596
9597        /**
9598         * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
9599         * a chance to become active and/or visible.
9600         * @param event The touch event
9601         */
9602        public boolean onTouchEvent(MotionEvent event);
9603
9604        /**
9605         * Called when the view is detached from window. Perform house keeping task, such as
9606         * stopping Runnable thread that would otherwise keep a reference on the context, thus
9607         * preventing the activity from being recycled.
9608         */
9609        public void onDetached();
9610    }
9611
9612    private class InsertionPointCursorController implements CursorController {
9613        private static final int DELAY_BEFORE_PASTE = 2000;
9614
9615        private InsertionHandleView mHandle;
9616
9617        public void show() {
9618            ((InsertionHandleView) getHandle()).show(DELAY_BEFORE_PASTE);
9619        }
9620
9621        public void showWithPaste() {
9622            ((InsertionHandleView) getHandle()).show(0);
9623        }
9624
9625        public void hide() {
9626            if (mHandle != null) {
9627                mHandle.hide();
9628            }
9629        }
9630
9631        public boolean onTouchEvent(MotionEvent ev) {
9632            return false;
9633        }
9634
9635        public void onTouchModeChanged(boolean isInTouchMode) {
9636            if (!isInTouchMode) {
9637                hide();
9638            }
9639        }
9640
9641        private HandleView getHandle() {
9642            if (mHandle == null) {
9643                mHandle = new InsertionHandleView();
9644            }
9645            return mHandle;
9646        }
9647
9648        @Override
9649        public void onDetached() {
9650            final ViewTreeObserver observer = getViewTreeObserver();
9651            observer.removeOnTouchModeChangeListener(this);
9652
9653            if (mHandle != null) mHandle.onDetached();
9654        }
9655    }
9656
9657    private class SelectionModifierCursorController implements CursorController {
9658        // The cursor controller handles, lazily created when shown.
9659        private SelectionStartHandleView mStartHandle;
9660        private SelectionEndHandleView mEndHandle;
9661        // The offsets of that last touch down event. Remembered to start selection there.
9662        private int mMinTouchOffset, mMaxTouchOffset;
9663
9664        // Double tap detection
9665        private long mPreviousTapUpTime = 0;
9666        private float mPreviousTapPositionX, mPreviousTapPositionY;
9667
9668        SelectionModifierCursorController() {
9669            resetTouchOffsets();
9670        }
9671
9672        public void show() {
9673            if (isInBatchEditMode()) {
9674                return;
9675            }
9676
9677            // Lazy object creation has to be done before updatePosition() is called.
9678            if (mStartHandle == null) mStartHandle = new SelectionStartHandleView();
9679            if (mEndHandle == null) mEndHandle = new SelectionEndHandleView();
9680
9681            mStartHandle.show();
9682            mEndHandle.show();
9683
9684            hideInsertionPointCursorController();
9685            hideSuggestions();
9686        }
9687
9688        public void hide() {
9689            if (mStartHandle != null) mStartHandle.hide();
9690            if (mEndHandle != null) mEndHandle.hide();
9691        }
9692
9693        public boolean onTouchEvent(MotionEvent event) {
9694            // This is done even when the View does not have focus, so that long presses can start
9695            // selection and tap can move cursor from this tap position.
9696            if (isTextEditable() || mTextIsSelectable) {
9697                switch (event.getActionMasked()) {
9698                    case MotionEvent.ACTION_DOWN:
9699                        final float x = event.getX();
9700                        final float y = event.getY();
9701
9702                        // Remember finger down position, to be able to start selection from there
9703                        mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
9704
9705                        // Double tap detection
9706                        long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
9707                        if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
9708                                isPositionOnText(x, y)) {
9709                            final float deltaX = x - mPreviousTapPositionX;
9710                            final float deltaY = y - mPreviousTapPositionY;
9711                            final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
9712                            if (distanceSquared < mSquaredTouchSlopDistance) {
9713                                showSuggestions();
9714                                mDiscardNextActionUp = true;
9715                            }
9716                        }
9717
9718                        mPreviousTapPositionX = x;
9719                        mPreviousTapPositionY = y;
9720
9721                        break;
9722
9723                    case MotionEvent.ACTION_POINTER_DOWN:
9724                    case MotionEvent.ACTION_POINTER_UP:
9725                        // Handle multi-point gestures. Keep min and max offset positions.
9726                        // Only activated for devices that correctly handle multi-touch.
9727                        if (mContext.getPackageManager().hasSystemFeature(
9728                                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
9729                            updateMinAndMaxOffsets(event);
9730                        }
9731                        break;
9732
9733                    case MotionEvent.ACTION_UP:
9734                        mPreviousTapUpTime = SystemClock.uptimeMillis();
9735                        break;
9736                }
9737            }
9738            return false;
9739        }
9740
9741        /**
9742         * @param event
9743         */
9744        private void updateMinAndMaxOffsets(MotionEvent event) {
9745            int pointerCount = event.getPointerCount();
9746            for (int index = 0; index < pointerCount; index++) {
9747                int offset = getOffsetForPosition(event.getX(index), event.getY(index));
9748                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
9749                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
9750            }
9751        }
9752
9753        public int getMinTouchOffset() {
9754            return mMinTouchOffset;
9755        }
9756
9757        public int getMaxTouchOffset() {
9758            return mMaxTouchOffset;
9759        }
9760
9761        public void resetTouchOffsets() {
9762            mMinTouchOffset = mMaxTouchOffset = -1;
9763        }
9764
9765        /**
9766         * @return true iff this controller is currently used to move the selection start.
9767         */
9768        public boolean isSelectionStartDragged() {
9769            return mStartHandle != null && mStartHandle.isDragging();
9770        }
9771
9772        public void onTouchModeChanged(boolean isInTouchMode) {
9773            if (!isInTouchMode) {
9774                hide();
9775            }
9776        }
9777
9778        @Override
9779        public void onDetached() {
9780            final ViewTreeObserver observer = getViewTreeObserver();
9781            observer.removeOnTouchModeChangeListener(this);
9782
9783            if (mStartHandle != null) mStartHandle.onDetached();
9784            if (mEndHandle != null) mEndHandle.onDetached();
9785        }
9786    }
9787
9788    private void hideInsertionPointCursorController() {
9789        // No need to create the controller to hide it.
9790        if (mInsertionPointCursorController != null) {
9791            mInsertionPointCursorController.hide();
9792        }
9793    }
9794
9795    /**
9796     * Hides the insertion controller and stops text selection mode, hiding the selection controller
9797     */
9798    private void hideControllers() {
9799        hideInsertionPointCursorController();
9800        stopSelectionActionMode();
9801        hideSuggestions();
9802    }
9803
9804    /**
9805     * Get the character offset closest to the specified absolute position. A typical use case is to
9806     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9807     *
9808     * @param x The horizontal absolute position of a point on screen
9809     * @param y The vertical absolute position of a point on screen
9810     * @return the character offset for the character whose position is closest to the specified
9811     *  position. Returns -1 if there is no layout.
9812     */
9813    public int getOffsetForPosition(float x, float y) {
9814        if (getLayout() == null) return -1;
9815        final int line = getLineAtCoordinate(y);
9816        final int offset = getOffsetAtCoordinate(line, x);
9817        return offset;
9818    }
9819
9820    private float convertToLocalHorizontalCoordinate(float x) {
9821        x -= getTotalPaddingLeft();
9822        // Clamp the position to inside of the view.
9823        x = Math.max(0.0f, x);
9824        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9825        x += getScrollX();
9826        return x;
9827    }
9828
9829    private int getLineAtCoordinate(float y) {
9830        y -= getTotalPaddingTop();
9831        // Clamp the position to inside of the view.
9832        y = Math.max(0.0f, y);
9833        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9834        y += getScrollY();
9835        return getLayout().getLineForVertical((int) y);
9836    }
9837
9838    private int getOffsetAtCoordinate(int line, float x) {
9839        x = convertToLocalHorizontalCoordinate(x);
9840        return getLayout().getOffsetForHorizontal(line, x);
9841    }
9842
9843    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
9844     * in the view. Returns false when the position is in the empty space of left/right of text.
9845     */
9846    private boolean isPositionOnText(float x, float y) {
9847        if (getLayout() == null) return false;
9848
9849        final int line = getLineAtCoordinate(y);
9850        x = convertToLocalHorizontalCoordinate(x);
9851
9852        if (x < getLayout().getLineLeft(line)) return false;
9853        if (x > getLayout().getLineRight(line)) return false;
9854        return true;
9855    }
9856
9857    @Override
9858    public boolean onDragEvent(DragEvent event) {
9859        switch (event.getAction()) {
9860            case DragEvent.ACTION_DRAG_STARTED:
9861                return hasInsertionController();
9862
9863            case DragEvent.ACTION_DRAG_ENTERED:
9864                TextView.this.requestFocus();
9865                return true;
9866
9867            case DragEvent.ACTION_DRAG_LOCATION:
9868                final int offset = getOffsetForPosition(event.getX(), event.getY());
9869                Selection.setSelection((Spannable)mText, offset);
9870                return true;
9871
9872            case DragEvent.ACTION_DROP:
9873                onDrop(event);
9874                return true;
9875
9876            case DragEvent.ACTION_DRAG_ENDED:
9877            case DragEvent.ACTION_DRAG_EXITED:
9878            default:
9879                return true;
9880        }
9881    }
9882
9883    private void onDrop(DragEvent event) {
9884        StringBuilder content = new StringBuilder("");
9885        ClipData clipData = event.getClipData();
9886        final int itemCount = clipData.getItemCount();
9887        for (int i=0; i < itemCount; i++) {
9888            Item item = clipData.getItemAt(i);
9889            content.append(item.coerceToText(TextView.this.mContext));
9890        }
9891
9892        final int offset = getOffsetForPosition(event.getX(), event.getY());
9893
9894        Object localState = event.getLocalState();
9895        DragLocalState dragLocalState = null;
9896        if (localState instanceof DragLocalState) {
9897            dragLocalState = (DragLocalState) localState;
9898        }
9899        boolean dragDropIntoItself = dragLocalState != null &&
9900                dragLocalState.sourceTextView == this;
9901
9902        if (dragDropIntoItself) {
9903            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
9904                // A drop inside the original selection discards the drop.
9905                return;
9906            }
9907        }
9908
9909        final int originalLength = mText.length();
9910        long minMax = prepareSpacesAroundPaste(offset, offset, content);
9911        int min = extractRangeStartFromLong(minMax);
9912        int max = extractRangeEndFromLong(minMax);
9913
9914        Selection.setSelection((Spannable) mText, max);
9915        ((Editable) mText).replace(min, max, content);
9916
9917        if (dragDropIntoItself) {
9918            int dragSourceStart = dragLocalState.start;
9919            int dragSourceEnd = dragLocalState.end;
9920            if (max <= dragSourceStart) {
9921                // Inserting text before selection has shifted positions
9922                final int shift = mText.length() - originalLength;
9923                dragSourceStart += shift;
9924                dragSourceEnd += shift;
9925            }
9926
9927            // Delete original selection
9928            ((Editable) mText).delete(dragSourceStart, dragSourceEnd);
9929
9930            // Make sure we do not leave two adjacent spaces.
9931            if ((dragSourceStart == 0 ||
9932                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
9933                    (dragSourceStart == mText.length() ||
9934                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
9935                final int pos = dragSourceStart == mText.length() ?
9936                        dragSourceStart - 1 : dragSourceStart;
9937                ((Editable) mText).delete(pos, pos + 1);
9938            }
9939        }
9940    }
9941
9942    /**
9943     * @return True if this view supports insertion handles.
9944     */
9945    boolean hasInsertionController() {
9946        return mInsertionControllerEnabled;
9947    }
9948
9949    /**
9950     * @return True if this view supports selection handles.
9951     */
9952    boolean hasSelectionController() {
9953        return mSelectionControllerEnabled;
9954    }
9955
9956    InsertionPointCursorController getInsertionController() {
9957        if (!mInsertionControllerEnabled) {
9958            return null;
9959        }
9960
9961        if (mInsertionPointCursorController == null) {
9962            mInsertionPointCursorController = new InsertionPointCursorController();
9963
9964            final ViewTreeObserver observer = getViewTreeObserver();
9965            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
9966        }
9967
9968        return mInsertionPointCursorController;
9969    }
9970
9971    SelectionModifierCursorController getSelectionController() {
9972        if (!mSelectionControllerEnabled) {
9973            return null;
9974        }
9975
9976        if (mSelectionModifierCursorController == null) {
9977            mSelectionModifierCursorController = new SelectionModifierCursorController();
9978
9979            final ViewTreeObserver observer = getViewTreeObserver();
9980            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
9981        }
9982
9983        return mSelectionModifierCursorController;
9984    }
9985
9986    boolean isInBatchEditMode() {
9987        final InputMethodState ims = mInputMethodState;
9988        if (ims != null) {
9989            return ims.mBatchEditNesting > 0;
9990        }
9991        return mInBatchEditControllers;
9992    }
9993
9994    @ViewDebug.ExportedProperty(category = "text")
9995    private CharSequence            mText;
9996    private CharSequence            mTransformed;
9997    private BufferType              mBufferType = BufferType.NORMAL;
9998
9999    private int                     mInputType = EditorInfo.TYPE_NULL;
10000    private CharSequence            mHint;
10001    private Layout                  mHintLayout;
10002
10003    private KeyListener             mInput;
10004
10005    private MovementMethod          mMovement;
10006    private TransformationMethod    mTransformation;
10007    private ChangeWatcher           mChangeWatcher;
10008
10009    private ArrayList<TextWatcher>  mListeners = null;
10010
10011    // display attributes
10012    private final TextPaint         mTextPaint;
10013    private boolean                 mUserSetTextScaleX;
10014    private final Paint             mHighlightPaint;
10015    private int                     mHighlightColor = 0xCC475925;
10016    /**
10017     * This is temporarily visible to fix bug 3085564 in webView. Do not rely on
10018     * this field being protected. Will be restored as private when lineHeight
10019     * feature request 3215097 is implemented
10020     * @hide
10021     */
10022    protected Layout                mLayout;
10023
10024    private long                    mShowCursor;
10025    private Blink                   mBlink;
10026    private boolean                 mCursorVisible = true;
10027
10028    // Cursor Controllers.
10029    private InsertionPointCursorController mInsertionPointCursorController;
10030    private SelectionModifierCursorController mSelectionModifierCursorController;
10031    private ActionMode              mSelectionActionMode;
10032    private boolean                 mInsertionControllerEnabled;
10033    private boolean                 mSelectionControllerEnabled;
10034    private boolean                 mInBatchEditControllers;
10035
10036    // These are needed to desambiguate a long click. If the long click comes from ones of these, we
10037    // select from the current cursor position. Otherwise, select from long pressed position.
10038    private boolean                 mDPadCenterIsDown = false;
10039    private boolean                 mEnterKeyIsDown = false;
10040    private boolean                 mContextMenuTriggeredByKey = false;
10041    // Created once and shared by different CursorController helper methods.
10042    // Only one cursor controller is active at any time which prevent race conditions.
10043    private static Rect             sCursorControllerTempRect = new Rect();
10044
10045    private boolean                 mSelectAllOnFocus = false;
10046
10047    private int                     mGravity = Gravity.TOP | Gravity.START;
10048    private boolean                 mHorizontallyScrolling;
10049
10050    private int                     mAutoLinkMask;
10051    private boolean                 mLinksClickable = true;
10052
10053    private float                   mSpacingMult = 1;
10054    private float                   mSpacingAdd = 0;
10055    private boolean                 mTextIsSelectable = false;
10056
10057    private static final int        LINES = 1;
10058    private static final int        EMS = LINES;
10059    private static final int        PIXELS = 2;
10060
10061    private int                     mMaximum = Integer.MAX_VALUE;
10062    private int                     mMaxMode = LINES;
10063    private int                     mMinimum = 0;
10064    private int                     mMinMode = LINES;
10065
10066    private int                     mMaxWidth = Integer.MAX_VALUE;
10067    private int                     mMaxWidthMode = PIXELS;
10068    private int                     mMinWidth = 0;
10069    private int                     mMinWidthMode = PIXELS;
10070
10071    private boolean                 mSingleLine;
10072    private int                     mDesiredHeightAtMeasure = -1;
10073    private boolean                 mIncludePad = true;
10074
10075    // tmp primitives, so we don't alloc them on each draw
10076    private Path                    mHighlightPath;
10077    private boolean                 mHighlightPathBogus = true;
10078    private static final RectF      sTempRect = new RectF();
10079
10080    // XXX should be much larger
10081    private static final int        VERY_WIDE = 16384;
10082
10083    private static final int        BLINK = 500;
10084
10085    private static final int ANIMATED_SCROLL_GAP = 250;
10086    private long mLastScroll;
10087    private Scroller mScroller = null;
10088
10089    private BoringLayout.Metrics mBoring;
10090    private BoringLayout.Metrics mHintBoring;
10091
10092    private BoringLayout mSavedLayout, mSavedHintLayout;
10093
10094    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
10095    private InputFilter[] mFilters = NO_FILTERS;
10096    private static final Spanned EMPTY_SPANNED = new SpannedString("");
10097    private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
10098    // System wide time for last cut or copy action.
10099    private static long sLastCutOrCopyTime;
10100    // Used to highlight a word when it is corrected by the IME
10101    private CorrectionHighlighter mCorrectionHighlighter;
10102    // New state used to change background based on whether this TextView is multiline.
10103    private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
10104}
10105