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