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