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