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