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