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