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