TextView.java revision d7d3ad477cce27bf968b5f07691b958bfea22c34
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import com.android.internal.util.FastMath;
20import com.android.internal.widget.EditableInputConnection;
21
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.R;
25import android.content.ClipData;
26import android.content.ClipboardManager;
27import android.content.Context;
28import android.content.pm.PackageManager;
29import android.content.res.ColorStateList;
30import android.content.res.Resources;
31import android.content.res.TypedArray;
32import android.content.res.XmlResourceParser;
33import android.graphics.Canvas;
34import android.graphics.Paint;
35import android.graphics.Path;
36import android.graphics.Rect;
37import android.graphics.RectF;
38import android.graphics.Typeface;
39import android.graphics.drawable.Drawable;
40import android.inputmethodservice.ExtractEditText;
41import android.net.Uri;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.Message;
45import android.os.Parcel;
46import android.os.Parcelable;
47import android.os.ResultReceiver;
48import android.os.SystemClock;
49import android.text.BoringLayout;
50import android.text.DynamicLayout;
51import android.text.Editable;
52import android.text.GetChars;
53import android.text.GraphicsOperations;
54import android.text.InputFilter;
55import android.text.InputType;
56import android.text.Layout;
57import android.text.ParcelableSpan;
58import android.text.Selection;
59import android.text.SpanWatcher;
60import android.text.Spannable;
61import android.text.SpannableString;
62import android.text.Spanned;
63import android.text.SpannedString;
64import android.text.StaticLayout;
65import android.text.TextPaint;
66import android.text.TextUtils;
67import android.text.TextWatcher;
68import android.text.method.DateKeyListener;
69import android.text.method.DateTimeKeyListener;
70import android.text.method.DialerKeyListener;
71import android.text.method.DigitsKeyListener;
72import android.text.method.KeyListener;
73import android.text.method.LinkMovementMethod;
74import android.text.method.MetaKeyKeyListener;
75import android.text.method.MovementMethod;
76import android.text.method.PasswordTransformationMethod;
77import android.text.method.SingleLineTransformationMethod;
78import android.text.method.TextKeyListener;
79import android.text.method.TimeKeyListener;
80import android.text.method.TransformationMethod;
81import android.text.style.ParagraphStyle;
82import android.text.style.URLSpan;
83import android.text.style.UpdateAppearance;
84import android.text.util.Linkify;
85import android.util.AttributeSet;
86import android.util.FloatMath;
87import android.util.Log;
88import android.util.TypedValue;
89import android.view.ActionMode;
90import android.view.ContextMenu;
91import android.view.Gravity;
92import android.view.HapticFeedbackConstants;
93import android.view.KeyEvent;
94import android.view.LayoutInflater;
95import android.view.Menu;
96import android.view.MenuItem;
97import android.view.MotionEvent;
98import android.view.View;
99import android.view.ViewConfiguration;
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        stopSelectionActionMode();
4529
4530        switch (keyCode) {
4531            case KeyEvent.KEYCODE_DPAD_CENTER:
4532                mDPadCenterIsDown = false;
4533                /*
4534                 * If there is a click listener, just call through to
4535                 * super, which will invoke it.
4536                 *
4537                 * If there isn't a click listener, try to show the soft
4538                 * input method.  (It will also
4539                 * call performClick(), but that won't do anything in
4540                 * this case.)
4541                 */
4542                if (mOnClickListener == null) {
4543                    if (mMovement != null && mText instanceof Editable
4544                            && mLayout != null && onCheckIsTextEditor()) {
4545                        InputMethodManager imm = (InputMethodManager)
4546                                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
4547                        imm.showSoftInput(this, 0);
4548                    }
4549                }
4550                return super.onKeyUp(keyCode, event);
4551
4552            case KeyEvent.KEYCODE_ENTER:
4553                mEnterKeyIsDown = false;
4554                if (mInputContentType != null
4555                        && mInputContentType.onEditorActionListener != null
4556                        && mInputContentType.enterDown) {
4557                    mInputContentType.enterDown = false;
4558                    if (mInputContentType.onEditorActionListener.onEditorAction(
4559                            this, EditorInfo.IME_NULL, event)) {
4560                        return true;
4561                    }
4562                }
4563
4564                if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4565                        || shouldAdvanceFocusOnEnter()) {
4566                    /*
4567                     * If there is a click listener, just call through to
4568                     * super, which will invoke it.
4569                     *
4570                     * If there isn't a click listener, try to advance focus,
4571                     * but still call through to super, which will reset the
4572                     * pressed state and longpress state.  (It will also
4573                     * call performClick(), but that won't do anything in
4574                     * this case.)
4575                     */
4576                    if (mOnClickListener == null) {
4577                        View v = focusSearch(FOCUS_DOWN);
4578
4579                        if (v != null) {
4580                            if (!v.requestFocus(FOCUS_DOWN)) {
4581                                throw new IllegalStateException("focus search returned a view " +
4582                                        "that wasn't able to take focus!");
4583                            }
4584
4585                            /*
4586                             * Return true because we handled the key; super
4587                             * will return false because there was no click
4588                             * listener.
4589                             */
4590                            super.onKeyUp(keyCode, event);
4591                            return true;
4592                        } else if ((event.getFlags()
4593                                & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4594                            // No target for next focus, but make sure the IME
4595                            // if this came from it.
4596                            InputMethodManager imm = InputMethodManager.peekInstance();
4597                            if (imm != null) {
4598                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
4599                            }
4600                        }
4601                    }
4602
4603                    return super.onKeyUp(keyCode, event);
4604                }
4605                break;
4606        }
4607
4608        if (mInput != null)
4609            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
4610                return true;
4611
4612        if (mMovement != null && mLayout != null)
4613            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
4614                return true;
4615
4616        return super.onKeyUp(keyCode, event);
4617    }
4618
4619    @Override public boolean onCheckIsTextEditor() {
4620        return mInputType != EditorInfo.TYPE_NULL;
4621    }
4622
4623    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4624        if (onCheckIsTextEditor()) {
4625            if (mInputMethodState == null) {
4626                mInputMethodState = new InputMethodState();
4627            }
4628            outAttrs.inputType = mInputType;
4629            if (mInputContentType != null) {
4630                outAttrs.imeOptions = mInputContentType.imeOptions;
4631                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
4632                outAttrs.actionLabel = mInputContentType.imeActionLabel;
4633                outAttrs.actionId = mInputContentType.imeActionId;
4634                outAttrs.extras = mInputContentType.extras;
4635            } else {
4636                outAttrs.imeOptions = EditorInfo.IME_NULL;
4637            }
4638            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
4639                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
4640                if (focusSearch(FOCUS_DOWN) != null) {
4641                    // An action has not been set, but the enter key will move to
4642                    // the next focus, so set the action to that.
4643                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
4644                } else {
4645                    // An action has not been set, and there is no focus to move
4646                    // to, so let's just supply a "done" action.
4647                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
4648                }
4649                if (!shouldAdvanceFocusOnEnter()) {
4650                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4651                }
4652            }
4653            if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS
4654                    | InputType.TYPE_TEXT_FLAG_MULTI_LINE))
4655                    == (InputType.TYPE_CLASS_TEXT
4656                            | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) {
4657                // Multi-line text editors should always show an enter key.
4658                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4659            }
4660            outAttrs.hintText = mHint;
4661            if (mText instanceof Editable) {
4662                InputConnection ic = new EditableInputConnection(this);
4663                outAttrs.initialSelStart = getSelectionStart();
4664                outAttrs.initialSelEnd = getSelectionEnd();
4665                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
4666                return ic;
4667            }
4668        }
4669        return null;
4670    }
4671
4672    /**
4673     * If this TextView contains editable content, extract a portion of it
4674     * based on the information in <var>request</var> in to <var>outText</var>.
4675     * @return Returns true if the text was successfully extracted, else false.
4676     */
4677    public boolean extractText(ExtractedTextRequest request,
4678            ExtractedText outText) {
4679        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
4680                EXTRACT_UNKNOWN, outText);
4681    }
4682
4683    static final int EXTRACT_NOTHING = -2;
4684    static final int EXTRACT_UNKNOWN = -1;
4685
4686    boolean extractTextInternal(ExtractedTextRequest request,
4687            int partialStartOffset, int partialEndOffset, int delta,
4688            ExtractedText outText) {
4689        final CharSequence content = mText;
4690        if (content != null) {
4691            if (partialStartOffset != EXTRACT_NOTHING) {
4692                final int N = content.length();
4693                if (partialStartOffset < 0) {
4694                    outText.partialStartOffset = outText.partialEndOffset = -1;
4695                    partialStartOffset = 0;
4696                    partialEndOffset = N;
4697                } else {
4698                    // Now use the delta to determine the actual amount of text
4699                    // we need.
4700                    partialEndOffset += delta;
4701                    // Adjust offsets to ensure we contain full spans.
4702                    if (content instanceof Spanned) {
4703                        Spanned spanned = (Spanned)content;
4704                        Object[] spans = spanned.getSpans(partialStartOffset,
4705                                partialEndOffset, ParcelableSpan.class);
4706                        int i = spans.length;
4707                        while (i > 0) {
4708                            i--;
4709                            int j = spanned.getSpanStart(spans[i]);
4710                            if (j < partialStartOffset) partialStartOffset = j;
4711                            j = spanned.getSpanEnd(spans[i]);
4712                            if (j > partialEndOffset) partialEndOffset = j;
4713                        }
4714                    }
4715                    outText.partialStartOffset = partialStartOffset;
4716                    outText.partialEndOffset = partialEndOffset - delta;
4717
4718                    if (partialStartOffset > N) {
4719                        partialStartOffset = N;
4720                    } else if (partialStartOffset < 0) {
4721                        partialStartOffset = 0;
4722                    }
4723                    if (partialEndOffset > N) {
4724                        partialEndOffset = N;
4725                    } else if (partialEndOffset < 0) {
4726                        partialEndOffset = 0;
4727                    }
4728                }
4729                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
4730                    outText.text = content.subSequence(partialStartOffset,
4731                            partialEndOffset);
4732                } else {
4733                    outText.text = TextUtils.substring(content, partialStartOffset,
4734                            partialEndOffset);
4735                }
4736            } else {
4737                outText.partialStartOffset = 0;
4738                outText.partialEndOffset = 0;
4739                outText.text = "";
4740            }
4741            outText.flags = 0;
4742            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
4743                outText.flags |= ExtractedText.FLAG_SELECTING;
4744            }
4745            if (mSingleLine) {
4746                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
4747            }
4748            outText.startOffset = 0;
4749            outText.selectionStart = getSelectionStart();
4750            outText.selectionEnd = getSelectionEnd();
4751            return true;
4752        }
4753        return false;
4754    }
4755
4756    boolean reportExtractedText() {
4757        final InputMethodState ims = mInputMethodState;
4758        if (ims != null) {
4759            final boolean contentChanged = ims.mContentChanged;
4760            if (contentChanged || ims.mSelectionModeChanged) {
4761                ims.mContentChanged = false;
4762                ims.mSelectionModeChanged = false;
4763                final ExtractedTextRequest req = mInputMethodState.mExtracting;
4764                if (req != null) {
4765                    InputMethodManager imm = InputMethodManager.peekInstance();
4766                    if (imm != null) {
4767                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
4768                                + ims.mChangedStart + " end=" + ims.mChangedEnd
4769                                + " delta=" + ims.mChangedDelta);
4770                        if (ims.mChangedStart < 0 && !contentChanged) {
4771                            ims.mChangedStart = EXTRACT_NOTHING;
4772                        }
4773                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
4774                                ims.mChangedDelta, ims.mTmpExtracted)) {
4775                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
4776                                    + ims.mTmpExtracted.partialStartOffset
4777                                    + " end=" + ims.mTmpExtracted.partialEndOffset
4778                                    + ": " + ims.mTmpExtracted.text);
4779                            imm.updateExtractedText(this, req.token,
4780                                    mInputMethodState.mTmpExtracted);
4781                            ims.mChangedStart = EXTRACT_UNKNOWN;
4782                            ims.mChangedEnd = EXTRACT_UNKNOWN;
4783                            ims.mChangedDelta = 0;
4784                            ims.mContentChanged = false;
4785                            return true;
4786                        }
4787                    }
4788                }
4789            }
4790        }
4791        return false;
4792    }
4793
4794    /**
4795     * This is used to remove all style-impacting spans from text before new
4796     * extracted text is being replaced into it, so that we don't have any
4797     * lingering spans applied during the replace.
4798     */
4799    static void removeParcelableSpans(Spannable spannable, int start, int end) {
4800        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
4801        int i = spans.length;
4802        while (i > 0) {
4803            i--;
4804            spannable.removeSpan(spans[i]);
4805        }
4806    }
4807
4808    /**
4809     * Apply to this text view the given extracted text, as previously
4810     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
4811     */
4812    public void setExtractedText(ExtractedText text) {
4813        Editable content = getEditableText();
4814        if (text.text != null) {
4815            if (content == null) {
4816                setText(text.text, TextView.BufferType.EDITABLE);
4817            } else if (text.partialStartOffset < 0) {
4818                removeParcelableSpans(content, 0, content.length());
4819                content.replace(0, content.length(), text.text);
4820            } else {
4821                final int N = content.length();
4822                int start = text.partialStartOffset;
4823                if (start > N) start = N;
4824                int end = text.partialEndOffset;
4825                if (end > N) end = N;
4826                removeParcelableSpans(content, start, end);
4827                content.replace(start, end, text.text);
4828            }
4829        }
4830
4831        // Now set the selection position...  make sure it is in range, to
4832        // avoid crashes.  If this is a partial update, it is possible that
4833        // the underlying text may have changed, causing us problems here.
4834        // Also we just don't want to trust clients to do the right thing.
4835        Spannable sp = (Spannable)getText();
4836        final int N = sp.length();
4837        int start = text.selectionStart;
4838        if (start < 0) start = 0;
4839        else if (start > N) start = N;
4840        int end = text.selectionEnd;
4841        if (end < 0) end = 0;
4842        else if (end > N) end = N;
4843        Selection.setSelection(sp, start, end);
4844
4845        // Finally, update the selection mode.
4846        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
4847            MetaKeyKeyListener.startSelecting(this, sp);
4848        } else {
4849            MetaKeyKeyListener.stopSelecting(this, sp);
4850        }
4851    }
4852
4853    /**
4854     * @hide
4855     */
4856    public void setExtracting(ExtractedTextRequest req) {
4857        if (mInputMethodState != null) {
4858            mInputMethodState.mExtracting = req;
4859        }
4860        hideControllers();
4861    }
4862
4863    /**
4864     * Called by the framework in response to a text completion from
4865     * the current input method, provided by it calling
4866     * {@link InputConnection#commitCompletion
4867     * InputConnection.commitCompletion()}.  The default implementation does
4868     * nothing; text views that are supporting auto-completion should override
4869     * this to do their desired behavior.
4870     *
4871     * @param text The auto complete text the user has selected.
4872     */
4873    public void onCommitCompletion(CompletionInfo text) {
4874    }
4875
4876    public void beginBatchEdit() {
4877        final InputMethodState ims = mInputMethodState;
4878        if (ims != null) {
4879            int nesting = ++ims.mBatchEditNesting;
4880            if (nesting == 1) {
4881                ims.mCursorChanged = false;
4882                ims.mChangedDelta = 0;
4883                if (ims.mContentChanged) {
4884                    // We already have a pending change from somewhere else,
4885                    // so turn this into a full update.
4886                    ims.mChangedStart = 0;
4887                    ims.mChangedEnd = mText.length();
4888                } else {
4889                    ims.mChangedStart = EXTRACT_UNKNOWN;
4890                    ims.mChangedEnd = EXTRACT_UNKNOWN;
4891                    ims.mContentChanged = false;
4892                }
4893                onBeginBatchEdit();
4894            }
4895        }
4896    }
4897
4898    public void endBatchEdit() {
4899        final InputMethodState ims = mInputMethodState;
4900        if (ims != null) {
4901            int nesting = --ims.mBatchEditNesting;
4902            if (nesting == 0) {
4903                finishBatchEdit(ims);
4904            }
4905        }
4906    }
4907
4908    void ensureEndedBatchEdit() {
4909        final InputMethodState ims = mInputMethodState;
4910        if (ims != null && ims.mBatchEditNesting != 0) {
4911            ims.mBatchEditNesting = 0;
4912            finishBatchEdit(ims);
4913        }
4914    }
4915
4916    void finishBatchEdit(final InputMethodState ims) {
4917        onEndBatchEdit();
4918
4919        if (ims.mContentChanged || ims.mSelectionModeChanged) {
4920            updateAfterEdit();
4921            reportExtractedText();
4922        } else if (ims.mCursorChanged) {
4923            // Cheezy way to get us to report the current cursor location.
4924            invalidateCursor();
4925        }
4926    }
4927
4928    void updateAfterEdit() {
4929        invalidate();
4930        int curs = getSelectionStart();
4931
4932        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4933                             Gravity.BOTTOM) {
4934            registerForPreDraw();
4935        }
4936
4937        if (curs >= 0) {
4938            mHighlightPathBogus = true;
4939
4940            if (isFocused()) {
4941                mShowCursor = SystemClock.uptimeMillis();
4942                makeBlink();
4943            }
4944        }
4945
4946        checkForResize();
4947    }
4948
4949    /**
4950     * Called by the framework in response to a request to begin a batch
4951     * of edit operations through a call to link {@link #beginBatchEdit()}.
4952     */
4953    public void onBeginBatchEdit() {
4954    }
4955
4956    /**
4957     * Called by the framework in response to a request to end a batch
4958     * of edit operations through a call to link {@link #endBatchEdit}.
4959     */
4960    public void onEndBatchEdit() {
4961    }
4962
4963    /**
4964     * Called by the framework in response to a private command from the
4965     * current method, provided by it calling
4966     * {@link InputConnection#performPrivateCommand
4967     * InputConnection.performPrivateCommand()}.
4968     *
4969     * @param action The action name of the command.
4970     * @param data Any additional data for the command.  This may be null.
4971     * @return Return true if you handled the command, else false.
4972     */
4973    public boolean onPrivateIMECommand(String action, Bundle data) {
4974        return false;
4975    }
4976
4977    private void nullLayouts() {
4978        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
4979            mSavedLayout = (BoringLayout) mLayout;
4980        }
4981        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
4982            mSavedHintLayout = (BoringLayout) mHintLayout;
4983        }
4984
4985        mLayout = mHintLayout = null;
4986    }
4987
4988    /**
4989     * Make a new Layout based on the already-measured size of the view,
4990     * on the assumption that it was measured correctly at some point.
4991     */
4992    private void assumeLayout() {
4993        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4994
4995        if (width < 1) {
4996            width = 0;
4997        }
4998
4999        int physicalWidth = width;
5000
5001        if (mHorizontallyScrolling) {
5002            width = VERY_WIDE;
5003        }
5004
5005        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5006                      physicalWidth, false);
5007    }
5008
5009    /**
5010     * The width passed in is now the desired layout width,
5011     * not the full view width with padding.
5012     * {@hide}
5013     */
5014    protected void makeNewLayout(int w, int hintWidth,
5015                                 BoringLayout.Metrics boring,
5016                                 BoringLayout.Metrics hintBoring,
5017                                 int ellipsisWidth, boolean bringIntoView) {
5018        stopMarquee();
5019
5020        mHighlightPathBogus = true;
5021
5022        if (w < 0) {
5023            w = 0;
5024        }
5025        if (hintWidth < 0) {
5026            hintWidth = 0;
5027        }
5028
5029        Layout.Alignment alignment;
5030        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
5031            case Gravity.CENTER_HORIZONTAL:
5032                alignment = Layout.Alignment.ALIGN_CENTER;
5033                break;
5034
5035            case Gravity.RIGHT:
5036                // Note, Layout resolves ALIGN_OPPOSITE to left or
5037                // right based on the paragraph direction.
5038                alignment = Layout.Alignment.ALIGN_OPPOSITE;
5039                break;
5040
5041            default:
5042                alignment = Layout.Alignment.ALIGN_NORMAL;
5043        }
5044
5045        boolean shouldEllipsize = mEllipsize != null && mInput == null;
5046
5047        if (mText instanceof Spannable) {
5048            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
5049                    alignment, mSpacingMult,
5050                    mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
5051                    ellipsisWidth);
5052        } else {
5053            if (boring == UNKNOWN_BORING) {
5054                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
5055                                               mBoring);
5056                if (boring != null) {
5057                    mBoring = boring;
5058                }
5059            }
5060
5061            if (boring != null) {
5062                if (boring.width <= w &&
5063                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
5064                    if (mSavedLayout != null) {
5065                        mLayout = mSavedLayout.
5066                                replaceOrMake(mTransformed, mTextPaint,
5067                                w, alignment, mSpacingMult, mSpacingAdd,
5068                                boring, mIncludePad);
5069                    } else {
5070                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
5071                                w, alignment, mSpacingMult, mSpacingAdd,
5072                                boring, mIncludePad);
5073                    }
5074
5075                    mSavedLayout = (BoringLayout) mLayout;
5076                } else if (shouldEllipsize && boring.width <= w) {
5077                    if (mSavedLayout != null) {
5078                        mLayout = mSavedLayout.
5079                                replaceOrMake(mTransformed, mTextPaint,
5080                                w, alignment, mSpacingMult, mSpacingAdd,
5081                                boring, mIncludePad, mEllipsize,
5082                                ellipsisWidth);
5083                    } else {
5084                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
5085                                w, alignment, mSpacingMult, mSpacingAdd,
5086                                boring, mIncludePad, mEllipsize,
5087                                ellipsisWidth);
5088                    }
5089                } else if (shouldEllipsize) {
5090                    mLayout = new StaticLayout(mTransformed,
5091                                0, mTransformed.length(),
5092                                mTextPaint, w, alignment, mSpacingMult,
5093                                mSpacingAdd, mIncludePad, mEllipsize,
5094                                ellipsisWidth);
5095                } else {
5096                    mLayout = new StaticLayout(mTransformed, mTextPaint,
5097                            w, alignment, mSpacingMult, mSpacingAdd,
5098                            mIncludePad);
5099                }
5100            } else if (shouldEllipsize) {
5101                mLayout = new StaticLayout(mTransformed,
5102                            0, mTransformed.length(),
5103                            mTextPaint, w, alignment, mSpacingMult,
5104                            mSpacingAdd, mIncludePad, mEllipsize,
5105                            ellipsisWidth);
5106            } else {
5107                mLayout = new StaticLayout(mTransformed, mTextPaint,
5108                        w, alignment, mSpacingMult, mSpacingAdd,
5109                        mIncludePad);
5110            }
5111        }
5112
5113        shouldEllipsize = mEllipsize != null;
5114        mHintLayout = null;
5115
5116        if (mHint != null) {
5117            if (shouldEllipsize) hintWidth = w;
5118
5119            if (hintBoring == UNKNOWN_BORING) {
5120                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
5121                                                   mHintBoring);
5122                if (hintBoring != null) {
5123                    mHintBoring = hintBoring;
5124                }
5125            }
5126
5127            if (hintBoring != null) {
5128                if (hintBoring.width <= hintWidth &&
5129                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
5130                    if (mSavedHintLayout != null) {
5131                        mHintLayout = mSavedHintLayout.
5132                                replaceOrMake(mHint, mTextPaint,
5133                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5134                                hintBoring, mIncludePad);
5135                    } else {
5136                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
5137                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5138                                hintBoring, mIncludePad);
5139                    }
5140
5141                    mSavedHintLayout = (BoringLayout) mHintLayout;
5142                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5143                    if (mSavedHintLayout != null) {
5144                        mHintLayout = mSavedHintLayout.
5145                                replaceOrMake(mHint, mTextPaint,
5146                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5147                                hintBoring, mIncludePad, mEllipsize,
5148                                ellipsisWidth);
5149                    } else {
5150                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
5151                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
5152                                hintBoring, mIncludePad, mEllipsize,
5153                                ellipsisWidth);
5154                    }
5155                } else if (shouldEllipsize) {
5156                    mHintLayout = new StaticLayout(mHint,
5157                                0, mHint.length(),
5158                                mTextPaint, hintWidth, alignment, mSpacingMult,
5159                                mSpacingAdd, mIncludePad, mEllipsize,
5160                                ellipsisWidth);
5161                } else {
5162                    mHintLayout = new StaticLayout(mHint, mTextPaint,
5163                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
5164                            mIncludePad);
5165                }
5166            } else if (shouldEllipsize) {
5167                mHintLayout = new StaticLayout(mHint,
5168                            0, mHint.length(),
5169                            mTextPaint, hintWidth, alignment, mSpacingMult,
5170                            mSpacingAdd, mIncludePad, mEllipsize,
5171                            ellipsisWidth);
5172            } else {
5173                mHintLayout = new StaticLayout(mHint, mTextPaint,
5174                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
5175                        mIncludePad);
5176            }
5177        }
5178
5179        if (bringIntoView) {
5180            registerForPreDraw();
5181        }
5182
5183        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5184            if (!compressText(ellipsisWidth)) {
5185                final int height = mLayoutParams.height;
5186                // If the size of the view does not depend on the size of the text, try to
5187                // start the marquee immediately
5188                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
5189                    startMarquee();
5190                } else {
5191                    // Defer the start of the marquee until we know our width (see setFrame())
5192                    mRestartMarquee = true;
5193                }
5194            }
5195        }
5196
5197        // CursorControllers need a non-null mLayout
5198        prepareCursorControllers();
5199    }
5200
5201    private boolean compressText(float width) {
5202        if (isHardwareAccelerated()) return false;
5203
5204        // Only compress the text if it hasn't been compressed by the previous pass
5205        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
5206                mTextPaint.getTextScaleX() == 1.0f) {
5207            final float textWidth = mLayout.getLineWidth(0);
5208            final float overflow = (textWidth + 1.0f - width) / width;
5209            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
5210                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
5211                post(new Runnable() {
5212                    public void run() {
5213                        requestLayout();
5214                    }
5215                });
5216                return true;
5217            }
5218        }
5219
5220        return false;
5221    }
5222
5223    private static int desired(Layout layout) {
5224        int n = layout.getLineCount();
5225        CharSequence text = layout.getText();
5226        float max = 0;
5227
5228        // if any line was wrapped, we can't use it.
5229        // but it's ok for the last line not to have a newline
5230
5231        for (int i = 0; i < n - 1; i++) {
5232            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
5233                return -1;
5234        }
5235
5236        for (int i = 0; i < n; i++) {
5237            max = Math.max(max, layout.getLineWidth(i));
5238        }
5239
5240        return (int) FloatMath.ceil(max);
5241    }
5242
5243    /**
5244     * Set whether the TextView includes extra top and bottom padding to make
5245     * room for accents that go above the normal ascent and descent.
5246     * The default is true.
5247     *
5248     * @attr ref android.R.styleable#TextView_includeFontPadding
5249     */
5250    public void setIncludeFontPadding(boolean includepad) {
5251        mIncludePad = includepad;
5252
5253        if (mLayout != null) {
5254            nullLayouts();
5255            requestLayout();
5256            invalidate();
5257        }
5258    }
5259
5260    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
5261
5262    @Override
5263    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5264        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
5265        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
5266        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
5267        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
5268
5269        int width;
5270        int height;
5271
5272        BoringLayout.Metrics boring = UNKNOWN_BORING;
5273        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
5274
5275        int des = -1;
5276        boolean fromexisting = false;
5277
5278        if (widthMode == MeasureSpec.EXACTLY) {
5279            // Parent has told us how big to be. So be it.
5280            width = widthSize;
5281        } else {
5282            if (mLayout != null && mEllipsize == null) {
5283                des = desired(mLayout);
5284            }
5285
5286            if (des < 0) {
5287                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
5288                if (boring != null) {
5289                    mBoring = boring;
5290                }
5291            } else {
5292                fromexisting = true;
5293            }
5294
5295            if (boring == null || boring == UNKNOWN_BORING) {
5296                if (des < 0) {
5297                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
5298                }
5299
5300                width = des;
5301            } else {
5302                width = boring.width;
5303            }
5304
5305            final Drawables dr = mDrawables;
5306            if (dr != null) {
5307                width = Math.max(width, dr.mDrawableWidthTop);
5308                width = Math.max(width, dr.mDrawableWidthBottom);
5309            }
5310
5311            if (mHint != null) {
5312                int hintDes = -1;
5313                int hintWidth;
5314
5315                if (mHintLayout != null && mEllipsize == null) {
5316                    hintDes = desired(mHintLayout);
5317                }
5318
5319                if (hintDes < 0) {
5320                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
5321                    if (hintBoring != null) {
5322                        mHintBoring = hintBoring;
5323                    }
5324                }
5325
5326                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
5327                    if (hintDes < 0) {
5328                        hintDes = (int) FloatMath.ceil(
5329                                Layout.getDesiredWidth(mHint, mTextPaint));
5330                    }
5331
5332                    hintWidth = hintDes;
5333                } else {
5334                    hintWidth = hintBoring.width;
5335                }
5336
5337                if (hintWidth > width) {
5338                    width = hintWidth;
5339                }
5340            }
5341
5342            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
5343
5344            if (mMaxWidthMode == EMS) {
5345                width = Math.min(width, mMaxWidth * getLineHeight());
5346            } else {
5347                width = Math.min(width, mMaxWidth);
5348            }
5349
5350            if (mMinWidthMode == EMS) {
5351                width = Math.max(width, mMinWidth * getLineHeight());
5352            } else {
5353                width = Math.max(width, mMinWidth);
5354            }
5355
5356            // Check against our minimum width
5357            width = Math.max(width, getSuggestedMinimumWidth());
5358
5359            if (widthMode == MeasureSpec.AT_MOST) {
5360                width = Math.min(widthSize, width);
5361            }
5362        }
5363
5364        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5365        int unpaddedWidth = want;
5366        int hintWant = want;
5367
5368        if (mHorizontallyScrolling)
5369            want = VERY_WIDE;
5370
5371        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5372
5373        if (mLayout == null) {
5374            makeNewLayout(want, hintWant, boring, hintBoring,
5375                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5376        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5377                   (mLayout.getEllipsizedWidth() !=
5378                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5379            if (mHint == null && mEllipsize == null &&
5380                    want > mLayout.getWidth() &&
5381                    (mLayout instanceof BoringLayout ||
5382                            (fromexisting && des >= 0 && des <= want))) {
5383                mLayout.increaseWidthTo(want);
5384            } else {
5385                makeNewLayout(want, hintWant, boring, hintBoring,
5386                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5387            }
5388        } else {
5389            // Width has not changed.
5390        }
5391
5392        if (heightMode == MeasureSpec.EXACTLY) {
5393            // Parent has told us how big to be. So be it.
5394            height = heightSize;
5395            mDesiredHeightAtMeasure = -1;
5396        } else {
5397            int desired = getDesiredHeight();
5398
5399            height = desired;
5400            mDesiredHeightAtMeasure = desired;
5401
5402            if (heightMode == MeasureSpec.AT_MOST) {
5403                height = Math.min(desired, heightSize);
5404            }
5405        }
5406
5407        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
5408        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
5409            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
5410        }
5411
5412        /*
5413         * We didn't let makeNewLayout() register to bring the cursor into view,
5414         * so do it here if there is any possibility that it is needed.
5415         */
5416        if (mMovement != null ||
5417            mLayout.getWidth() > unpaddedWidth ||
5418            mLayout.getHeight() > unpaddedHeight) {
5419            registerForPreDraw();
5420        } else {
5421            scrollTo(0, 0);
5422        }
5423
5424        setMeasuredDimension(width, height);
5425    }
5426
5427    private int getDesiredHeight() {
5428        return Math.max(
5429                getDesiredHeight(mLayout, true),
5430                getDesiredHeight(mHintLayout, mEllipsize != null));
5431    }
5432
5433    private int getDesiredHeight(Layout layout, boolean cap) {
5434        if (layout == null) {
5435            return 0;
5436        }
5437
5438        int linecount = layout.getLineCount();
5439        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5440        int desired = layout.getLineTop(linecount);
5441
5442        final Drawables dr = mDrawables;
5443        if (dr != null) {
5444            desired = Math.max(desired, dr.mDrawableHeightLeft);
5445            desired = Math.max(desired, dr.mDrawableHeightRight);
5446        }
5447
5448        desired += pad;
5449
5450        if (mMaxMode == LINES) {
5451            /*
5452             * Don't cap the hint to a certain number of lines.
5453             * (Do cap it, though, if we have a maximum pixel height.)
5454             */
5455            if (cap) {
5456                if (linecount > mMaximum) {
5457                    desired = layout.getLineTop(mMaximum) +
5458                              layout.getBottomPadding();
5459
5460                    if (dr != null) {
5461                        desired = Math.max(desired, dr.mDrawableHeightLeft);
5462                        desired = Math.max(desired, dr.mDrawableHeightRight);
5463                    }
5464
5465                    desired += pad;
5466                    linecount = mMaximum;
5467                }
5468            }
5469        } else {
5470            desired = Math.min(desired, mMaximum);
5471        }
5472
5473        if (mMinMode == LINES) {
5474            if (linecount < mMinimum) {
5475                desired += getLineHeight() * (mMinimum - linecount);
5476            }
5477        } else {
5478            desired = Math.max(desired, mMinimum);
5479        }
5480
5481        // Check against our minimum height
5482        desired = Math.max(desired, getSuggestedMinimumHeight());
5483
5484        return desired;
5485    }
5486
5487    /**
5488     * Check whether a change to the existing text layout requires a
5489     * new view layout.
5490     */
5491    private void checkForResize() {
5492        boolean sizeChanged = false;
5493
5494        if (mLayout != null) {
5495            // Check if our width changed
5496            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
5497                sizeChanged = true;
5498                invalidate();
5499            }
5500
5501            // Check if our height changed
5502            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
5503                int desiredHeight = getDesiredHeight();
5504
5505                if (desiredHeight != this.getHeight()) {
5506                    sizeChanged = true;
5507                }
5508            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
5509                if (mDesiredHeightAtMeasure >= 0) {
5510                    int desiredHeight = getDesiredHeight();
5511
5512                    if (desiredHeight != mDesiredHeightAtMeasure) {
5513                        sizeChanged = true;
5514                    }
5515                }
5516            }
5517        }
5518
5519        if (sizeChanged) {
5520            requestLayout();
5521            // caller will have already invalidated
5522        }
5523    }
5524
5525    /**
5526     * Check whether entirely new text requires a new view layout
5527     * or merely a new text layout.
5528     */
5529    private void checkForRelayout() {
5530        // If we have a fixed width, we can just swap in a new text layout
5531        // if the text height stays the same or if the view height is fixed.
5532
5533        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
5534                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
5535                (mHint == null || mHintLayout != null) &&
5536                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
5537            // Static width, so try making a new text layout.
5538
5539            int oldht = mLayout.getHeight();
5540            int want = mLayout.getWidth();
5541            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5542
5543            /*
5544             * No need to bring the text into view, since the size is not
5545             * changing (unless we do the requestLayout(), in which case it
5546             * will happen at measure).
5547             */
5548            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5549                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
5550                          false);
5551
5552            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
5553                // In a fixed-height view, so use our new text layout.
5554                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
5555                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
5556                    invalidate();
5557                    return;
5558                }
5559
5560                // Dynamic height, but height has stayed the same,
5561                // so use our new text layout.
5562                if (mLayout.getHeight() == oldht &&
5563                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
5564                    invalidate();
5565                    return;
5566                }
5567            }
5568
5569            // We lose: the height has changed and we have a dynamic height.
5570            // Request a new view layout using our new text layout.
5571            requestLayout();
5572            invalidate();
5573        } else {
5574            // Dynamic width, so we have no choice but to request a new
5575            // view layout with a new text layout.
5576
5577            nullLayouts();
5578            requestLayout();
5579            invalidate();
5580        }
5581    }
5582
5583    /**
5584     * Returns true if anything changed.
5585     */
5586    private boolean bringTextIntoView() {
5587        int line = 0;
5588        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5589            line = mLayout.getLineCount() - 1;
5590        }
5591
5592        Layout.Alignment a = mLayout.getParagraphAlignment(line);
5593        int dir = mLayout.getParagraphDirection(line);
5594        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5595        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5596        int ht = mLayout.getHeight();
5597
5598        int scrollx, scrolly;
5599
5600        if (a == Layout.Alignment.ALIGN_CENTER) {
5601            /*
5602             * Keep centered if possible, or, if it is too wide to fit,
5603             * keep leading edge in view.
5604             */
5605
5606            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5607            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5608
5609            if (right - left < hspace) {
5610                scrollx = (right + left) / 2 - hspace / 2;
5611            } else {
5612                if (dir < 0) {
5613                    scrollx = right - hspace;
5614                } else {
5615                    scrollx = left;
5616                }
5617            }
5618        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
5619            /*
5620             * Keep leading edge in view.
5621             */
5622
5623            if (dir < 0) {
5624                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5625                scrollx = right - hspace;
5626            } else {
5627                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5628            }
5629        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
5630            /*
5631             * Keep trailing edge in view.
5632             */
5633
5634            if (dir < 0) {
5635                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5636            } else {
5637                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5638                scrollx = right - hspace;
5639            }
5640        }
5641
5642        if (ht < vspace) {
5643            scrolly = 0;
5644        } else {
5645            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5646                scrolly = ht - vspace;
5647            } else {
5648                scrolly = 0;
5649            }
5650        }
5651
5652        if (scrollx != mScrollX || scrolly != mScrollY) {
5653            scrollTo(scrollx, scrolly);
5654            return true;
5655        } else {
5656            return false;
5657        }
5658    }
5659
5660    /**
5661     * Move the point, specified by the offset, into the view if it is needed.
5662     * This has to be called after layout. Returns true if anything changed.
5663     */
5664    public boolean bringPointIntoView(int offset) {
5665        boolean changed = false;
5666
5667        int line = mLayout.getLineForOffset(offset);
5668
5669        // FIXME: Is it okay to truncate this, or should we round?
5670        final int x = (int)mLayout.getPrimaryHorizontal(offset);
5671        final int top = mLayout.getLineTop(line);
5672        final int bottom = mLayout.getLineTop(line + 1);
5673
5674        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5675        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5676        int ht = mLayout.getHeight();
5677
5678        int grav;
5679
5680        switch (mLayout.getParagraphAlignment(line)) {
5681            case ALIGN_NORMAL:
5682                grav = 1;
5683                break;
5684
5685            case ALIGN_OPPOSITE:
5686                grav = -1;
5687                break;
5688
5689            default:
5690                grav = 0;
5691        }
5692
5693        grav *= mLayout.getParagraphDirection(line);
5694
5695        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5696        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5697
5698        int hslack = (bottom - top) / 2;
5699        int vslack = hslack;
5700
5701        if (vslack > vspace / 4)
5702            vslack = vspace / 4;
5703        if (hslack > hspace / 4)
5704            hslack = hspace / 4;
5705
5706        int hs = mScrollX;
5707        int vs = mScrollY;
5708
5709        if (top - vs < vslack)
5710            vs = top - vslack;
5711        if (bottom - vs > vspace - vslack)
5712            vs = bottom - (vspace - vslack);
5713        if (ht - vs < vspace)
5714            vs = ht - vspace;
5715        if (0 - vs > 0)
5716            vs = 0;
5717
5718        if (grav != 0) {
5719            if (x - hs < hslack) {
5720                hs = x - hslack;
5721            }
5722            if (x - hs > hspace - hslack) {
5723                hs = x - (hspace - hslack);
5724            }
5725        }
5726
5727        if (grav < 0) {
5728            if (left - hs > 0)
5729                hs = left;
5730            if (right - hs < hspace)
5731                hs = right - hspace;
5732        } else if (grav > 0) {
5733            if (right - hs < hspace)
5734                hs = right - hspace;
5735            if (left - hs > 0)
5736                hs = left;
5737        } else /* grav == 0 */ {
5738            if (right - left <= hspace) {
5739                /*
5740                 * If the entire text fits, center it exactly.
5741                 */
5742                hs = left - (hspace - (right - left)) / 2;
5743            } else if (x > right - hslack) {
5744                /*
5745                 * If we are near the right edge, keep the right edge
5746                 * at the edge of the view.
5747                 */
5748                hs = right - hspace;
5749            } else if (x < left + hslack) {
5750                /*
5751                 * If we are near the left edge, keep the left edge
5752                 * at the edge of the view.
5753                 */
5754                hs = left;
5755            } else if (left > hs) {
5756                /*
5757                 * Is there whitespace visible at the left?  Fix it if so.
5758                 */
5759                hs = left;
5760            } else if (right < hs + hspace) {
5761                /*
5762                 * Is there whitespace visible at the right?  Fix it if so.
5763                 */
5764                hs = right - hspace;
5765            } else {
5766                /*
5767                 * Otherwise, float as needed.
5768                 */
5769                if (x - hs < hslack) {
5770                    hs = x - hslack;
5771                }
5772                if (x - hs > hspace - hslack) {
5773                    hs = x - (hspace - hslack);
5774                }
5775            }
5776        }
5777
5778        if (hs != mScrollX || vs != mScrollY) {
5779            if (mScroller == null) {
5780                scrollTo(hs, vs);
5781            } else {
5782                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
5783                int dx = hs - mScrollX;
5784                int dy = vs - mScrollY;
5785
5786                if (duration > ANIMATED_SCROLL_GAP) {
5787                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
5788                    awakenScrollBars(mScroller.getDuration());
5789                    invalidate();
5790                } else {
5791                    if (!mScroller.isFinished()) {
5792                        mScroller.abortAnimation();
5793                    }
5794
5795                    scrollBy(dx, dy);
5796                }
5797
5798                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
5799            }
5800
5801            changed = true;
5802        }
5803
5804        if (isFocused()) {
5805            // This offsets because getInterestingRect() is in terms of
5806            // viewport coordinates, but requestRectangleOnScreen()
5807            // is in terms of content coordinates.
5808
5809            Rect r = new Rect(x, top, x + 1, bottom);
5810            getInterestingRect(r, line);
5811            r.offset(mScrollX, mScrollY);
5812
5813            if (requestRectangleOnScreen(r)) {
5814                changed = true;
5815            }
5816        }
5817
5818        return changed;
5819    }
5820
5821    /**
5822     * Move the cursor, if needed, so that it is at an offset that is visible
5823     * to the user.  This will not move the cursor if it represents more than
5824     * one character (a selection range).  This will only work if the
5825     * TextView contains spannable text; otherwise it will do nothing.
5826     *
5827     * @return True if the cursor was actually moved, false otherwise.
5828     */
5829    public boolean moveCursorToVisibleOffset() {
5830        if (!(mText instanceof Spannable)) {
5831            return false;
5832        }
5833        int start = getSelectionStart();
5834        int end = getSelectionEnd();
5835        if (start != end) {
5836            return false;
5837        }
5838
5839        // First: make sure the line is visible on screen:
5840
5841        int line = mLayout.getLineForOffset(start);
5842
5843        final int top = mLayout.getLineTop(line);
5844        final int bottom = mLayout.getLineTop(line + 1);
5845        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5846        int vslack = (bottom - top) / 2;
5847        if (vslack > vspace / 4)
5848            vslack = vspace / 4;
5849        final int vs = mScrollY;
5850
5851        if (top < (vs+vslack)) {
5852            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
5853        } else if (bottom > (vspace+vs-vslack)) {
5854            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
5855        }
5856
5857        // Next: make sure the character is visible on screen:
5858
5859        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5860        final int hs = mScrollX;
5861        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
5862        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
5863
5864        // line might contain bidirectional text
5865        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
5866        final int highChar = leftChar > rightChar ? leftChar : rightChar;
5867
5868        int newStart = start;
5869        if (newStart < lowChar) {
5870            newStart = lowChar;
5871        } else if (newStart > highChar) {
5872            newStart = highChar;
5873        }
5874
5875        if (newStart != start) {
5876            Selection.setSelection((Spannable)mText, newStart);
5877            return true;
5878        }
5879
5880        return false;
5881    }
5882
5883    @Override
5884    public void computeScroll() {
5885        if (mScroller != null) {
5886            if (mScroller.computeScrollOffset()) {
5887                mScrollX = mScroller.getCurrX();
5888                mScrollY = mScroller.getCurrY();
5889                postInvalidate();  // So we draw again
5890            }
5891        }
5892    }
5893
5894    private void getInterestingRect(Rect r, int line) {
5895        convertFromViewportToContentCoordinates(r);
5896
5897        // Rectangle can can be expanded on first and last line to take
5898        // padding into account.
5899        // TODO Take left/right padding into account too?
5900        if (line == 0) r.top -= getExtendedPaddingTop();
5901        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
5902    }
5903
5904    private void convertFromViewportToContentCoordinates(Rect r) {
5905        final int horizontalOffset = viewportToContentHorizontalOffset();
5906        r.left += horizontalOffset;
5907        r.right += horizontalOffset;
5908
5909        final int verticalOffset = viewportToContentVerticalOffset();
5910        r.top += verticalOffset;
5911        r.bottom += verticalOffset;
5912    }
5913
5914    private int viewportToContentHorizontalOffset() {
5915        return getCompoundPaddingLeft() - mScrollX;
5916    }
5917
5918    private int viewportToContentVerticalOffset() {
5919        int offset = getExtendedPaddingTop() - mScrollY;
5920        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5921            offset += getVerticalOffset(false);
5922        }
5923        return offset;
5924    }
5925
5926    @Override
5927    public void debug(int depth) {
5928        super.debug(depth);
5929
5930        String output = debugIndent(depth);
5931        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
5932                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
5933                + "} ";
5934
5935        if (mText != null) {
5936
5937            output += "mText=\"" + mText + "\" ";
5938            if (mLayout != null) {
5939                output += "mLayout width=" + mLayout.getWidth()
5940                        + " height=" + mLayout.getHeight();
5941            }
5942        } else {
5943            output += "mText=NULL";
5944        }
5945        Log.d(VIEW_LOG_TAG, output);
5946    }
5947
5948    /**
5949     * Convenience for {@link Selection#getSelectionStart}.
5950     */
5951    @ViewDebug.ExportedProperty(category = "text")
5952    public int getSelectionStart() {
5953        return Selection.getSelectionStart(getText());
5954    }
5955
5956    /**
5957     * Convenience for {@link Selection#getSelectionEnd}.
5958     */
5959    @ViewDebug.ExportedProperty(category = "text")
5960    public int getSelectionEnd() {
5961        return Selection.getSelectionEnd(getText());
5962    }
5963
5964    /**
5965     * Return true iff there is a selection inside this text view.
5966     */
5967    public boolean hasSelection() {
5968        final int selectionStart = getSelectionStart();
5969        final int selectionEnd = getSelectionEnd();
5970
5971        return selectionStart >= 0 && selectionStart != selectionEnd;
5972    }
5973
5974    /**
5975     * Sets the properties of this field (lines, horizontally scrolling,
5976     * transformation method) to be for a single-line input.
5977     *
5978     * @attr ref android.R.styleable#TextView_singleLine
5979     */
5980    public void setSingleLine() {
5981        setSingleLine(true);
5982    }
5983
5984    /**
5985     * If true, sets the properties of this field (lines, horizontally
5986     * scrolling, transformation method) to be for a single-line input;
5987     * if false, restores these to the default conditions.
5988     * Note that calling this with false restores default conditions,
5989     * not necessarily those that were in effect prior to calling
5990     * it with true.
5991     *
5992     * @attr ref android.R.styleable#TextView_singleLine
5993     */
5994    @android.view.RemotableViewMethod
5995    public void setSingleLine(boolean singleLine) {
5996        if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
5997                == EditorInfo.TYPE_CLASS_TEXT) {
5998            if (singleLine) {
5999                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6000            } else {
6001                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6002            }
6003        }
6004        applySingleLine(singleLine, true);
6005    }
6006
6007    private void applySingleLine(boolean singleLine, boolean applyTransformation) {
6008        mSingleLine = singleLine;
6009        if (singleLine) {
6010            setLines(1);
6011            setHorizontallyScrolling(true);
6012            if (applyTransformation) {
6013                setTransformationMethod(SingleLineTransformationMethod.
6014                                        getInstance());
6015            }
6016        } else {
6017            setMaxLines(Integer.MAX_VALUE);
6018            setHorizontallyScrolling(false);
6019            if (applyTransformation) {
6020                setTransformationMethod(null);
6021            }
6022        }
6023    }
6024
6025    /**
6026     * Causes words in the text that are longer than the view is wide
6027     * to be ellipsized instead of broken in the middle.  You may also
6028     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
6029     * to constrain the text to a single line.  Use <code>null</code>
6030     * to turn off ellipsizing.
6031     *
6032     * @attr ref android.R.styleable#TextView_ellipsize
6033     */
6034    public void setEllipsize(TextUtils.TruncateAt where) {
6035        mEllipsize = where;
6036
6037        if (mLayout != null) {
6038            nullLayouts();
6039            requestLayout();
6040            invalidate();
6041        }
6042    }
6043
6044    /**
6045     * Sets how many times to repeat the marquee animation. Only applied if the
6046     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
6047     *
6048     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
6049     */
6050    public void setMarqueeRepeatLimit(int marqueeLimit) {
6051        mMarqueeRepeatLimit = marqueeLimit;
6052    }
6053
6054    /**
6055     * Returns where, if anywhere, words that are longer than the view
6056     * is wide should be ellipsized.
6057     */
6058    @ViewDebug.ExportedProperty
6059    public TextUtils.TruncateAt getEllipsize() {
6060        return mEllipsize;
6061    }
6062
6063    /**
6064     * Set the TextView so that when it takes focus, all the text is
6065     * selected.
6066     *
6067     * @attr ref android.R.styleable#TextView_selectAllOnFocus
6068     */
6069    @android.view.RemotableViewMethod
6070    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
6071        mSelectAllOnFocus = selectAllOnFocus;
6072
6073        if (selectAllOnFocus && !(mText instanceof Spannable)) {
6074            setText(mText, BufferType.SPANNABLE);
6075        }
6076    }
6077
6078    /**
6079     * Set whether the cursor is visible.  The default is true.
6080     *
6081     * @attr ref android.R.styleable#TextView_cursorVisible
6082     */
6083    @android.view.RemotableViewMethod
6084    public void setCursorVisible(boolean visible) {
6085        mCursorVisible = visible;
6086        invalidate();
6087
6088        if (visible) {
6089            makeBlink();
6090        } else if (mBlink != null) {
6091            mBlink.removeCallbacks(mBlink);
6092        }
6093
6094        // InsertionPointCursorController depends on mCursorVisible
6095        prepareCursorControllers();
6096    }
6097
6098    private boolean canMarquee() {
6099        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
6100        return width > 0 && mLayout.getLineWidth(0) > width;
6101    }
6102
6103    private void startMarquee() {
6104        // Do not ellipsize EditText
6105        if (mInput != null) return;
6106
6107        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
6108            return;
6109        }
6110
6111        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
6112                getLineCount() == 1 && canMarquee()) {
6113
6114            if (mMarquee == null) mMarquee = new Marquee(this);
6115            mMarquee.start(mMarqueeRepeatLimit);
6116        }
6117    }
6118
6119    private void stopMarquee() {
6120        if (mMarquee != null && !mMarquee.isStopped()) {
6121            mMarquee.stop();
6122        }
6123    }
6124
6125    private void startStopMarquee(boolean start) {
6126        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6127            if (start) {
6128                startMarquee();
6129            } else {
6130                stopMarquee();
6131            }
6132        }
6133    }
6134
6135    private static final class Marquee extends Handler {
6136        // TODO: Add an option to configure this
6137        private static final float MARQUEE_DELTA_MAX = 0.07f;
6138        private static final int MARQUEE_DELAY = 1200;
6139        private static final int MARQUEE_RESTART_DELAY = 1200;
6140        private static final int MARQUEE_RESOLUTION = 1000 / 30;
6141        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
6142
6143        private static final byte MARQUEE_STOPPED = 0x0;
6144        private static final byte MARQUEE_STARTING = 0x1;
6145        private static final byte MARQUEE_RUNNING = 0x2;
6146
6147        private static final int MESSAGE_START = 0x1;
6148        private static final int MESSAGE_TICK = 0x2;
6149        private static final int MESSAGE_RESTART = 0x3;
6150
6151        private final WeakReference<TextView> mView;
6152
6153        private byte mStatus = MARQUEE_STOPPED;
6154        private final float mScrollUnit;
6155        private float mMaxScroll;
6156        float mMaxFadeScroll;
6157        private float mGhostStart;
6158        private float mGhostOffset;
6159        private float mFadeStop;
6160        private int mRepeatLimit;
6161
6162        float mScroll;
6163
6164        Marquee(TextView v) {
6165            final float density = v.getContext().getResources().getDisplayMetrics().density;
6166            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
6167            mView = new WeakReference<TextView>(v);
6168        }
6169
6170        @Override
6171        public void handleMessage(Message msg) {
6172            switch (msg.what) {
6173                case MESSAGE_START:
6174                    mStatus = MARQUEE_RUNNING;
6175                    tick();
6176                    break;
6177                case MESSAGE_TICK:
6178                    tick();
6179                    break;
6180                case MESSAGE_RESTART:
6181                    if (mStatus == MARQUEE_RUNNING) {
6182                        if (mRepeatLimit >= 0) {
6183                            mRepeatLimit--;
6184                        }
6185                        start(mRepeatLimit);
6186                    }
6187                    break;
6188            }
6189        }
6190
6191        void tick() {
6192            if (mStatus != MARQUEE_RUNNING) {
6193                return;
6194            }
6195
6196            removeMessages(MESSAGE_TICK);
6197
6198            final TextView textView = mView.get();
6199            if (textView != null && (textView.isFocused() || textView.isSelected())) {
6200                mScroll += mScrollUnit;
6201                if (mScroll > mMaxScroll) {
6202                    mScroll = mMaxScroll;
6203                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
6204                } else {
6205                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
6206                }
6207                textView.invalidate();
6208            }
6209        }
6210
6211        void stop() {
6212            mStatus = MARQUEE_STOPPED;
6213            removeMessages(MESSAGE_START);
6214            removeMessages(MESSAGE_RESTART);
6215            removeMessages(MESSAGE_TICK);
6216            resetScroll();
6217        }
6218
6219        private void resetScroll() {
6220            mScroll = 0.0f;
6221            final TextView textView = mView.get();
6222            if (textView != null) textView.invalidate();
6223        }
6224
6225        void start(int repeatLimit) {
6226            if (repeatLimit == 0) {
6227                stop();
6228                return;
6229            }
6230            mRepeatLimit = repeatLimit;
6231            final TextView textView = mView.get();
6232            if (textView != null && textView.mLayout != null) {
6233                mStatus = MARQUEE_STARTING;
6234                mScroll = 0.0f;
6235                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
6236                        textView.getCompoundPaddingRight();
6237                final float lineWidth = textView.mLayout.getLineWidth(0);
6238                final float gap = textWidth / 3.0f;
6239                mGhostStart = lineWidth - textWidth + gap;
6240                mMaxScroll = mGhostStart + textWidth;
6241                mGhostOffset = lineWidth + gap;
6242                mFadeStop = lineWidth + textWidth / 6.0f;
6243                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
6244
6245                textView.invalidate();
6246                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
6247            }
6248        }
6249
6250        float getGhostOffset() {
6251            return mGhostOffset;
6252        }
6253
6254        boolean shouldDrawLeftFade() {
6255            return mScroll <= mFadeStop;
6256        }
6257
6258        boolean shouldDrawGhost() {
6259            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
6260        }
6261
6262        boolean isRunning() {
6263            return mStatus == MARQUEE_RUNNING;
6264        }
6265
6266        boolean isStopped() {
6267            return mStatus == MARQUEE_STOPPED;
6268        }
6269    }
6270
6271    /**
6272     * This method is called when the text is changed, in case any
6273     * subclasses would like to know.
6274     *
6275     * @param text The text the TextView is displaying.
6276     * @param start The offset of the start of the range of the text
6277     *              that was modified.
6278     * @param before The offset of the former end of the range of the
6279     *               text that was modified.  If text was simply inserted,
6280     *               this will be the same as <code>start</code>.
6281     *               If text was replaced with new text or deleted, the
6282     *               length of the old text was <code>before-start</code>.
6283     * @param after The offset of the end of the range of the text
6284     *              that was modified.  If text was simply deleted,
6285     *              this will be the same as <code>start</code>.
6286     *              If text was replaced with new text or inserted,
6287     *              the length of the new text is <code>after-start</code>.
6288     */
6289    protected void onTextChanged(CharSequence text,
6290                                 int start, int before, int after) {
6291    }
6292
6293    /**
6294     * This method is called when the selection has changed, in case any
6295     * subclasses would like to know.
6296     *
6297     * @param selStart The new selection start location.
6298     * @param selEnd The new selection end location.
6299     */
6300    protected void onSelectionChanged(int selStart, int selEnd) {
6301    }
6302
6303    /**
6304     * Adds a TextWatcher to the list of those whose methods are called
6305     * whenever this TextView's text changes.
6306     * <p>
6307     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6308     * not called after {@link #setText} calls.  Now, doing {@link #setText}
6309     * if there are any text changed listeners forces the buffer type to
6310     * Editable if it would not otherwise be and does call this method.
6311     */
6312    public void addTextChangedListener(TextWatcher watcher) {
6313        if (mListeners == null) {
6314            mListeners = new ArrayList<TextWatcher>();
6315        }
6316
6317        mListeners.add(watcher);
6318    }
6319
6320    /**
6321     * Removes the specified TextWatcher from the list of those whose
6322     * methods are called
6323     * whenever this TextView's text changes.
6324     */
6325    public void removeTextChangedListener(TextWatcher watcher) {
6326        if (mListeners != null) {
6327            int i = mListeners.indexOf(watcher);
6328
6329            if (i >= 0) {
6330                mListeners.remove(i);
6331            }
6332        }
6333    }
6334
6335    private void sendBeforeTextChanged(CharSequence text, int start, int before,
6336                                   int after) {
6337        if (mListeners != null) {
6338            final ArrayList<TextWatcher> list = mListeners;
6339            final int count = list.size();
6340            for (int i = 0; i < count; i++) {
6341                list.get(i).beforeTextChanged(text, start, before, after);
6342            }
6343        }
6344    }
6345
6346    /**
6347     * Not private so it can be called from an inner class without going
6348     * through a thunk.
6349     */
6350    void sendOnTextChanged(CharSequence text, int start, int before,
6351                                   int after) {
6352        if (mListeners != null) {
6353            final ArrayList<TextWatcher> list = mListeners;
6354            final int count = list.size();
6355            for (int i = 0; i < count; i++) {
6356                list.get(i).onTextChanged(text, start, before, after);
6357            }
6358        }
6359    }
6360
6361    /**
6362     * Not private so it can be called from an inner class without going
6363     * through a thunk.
6364     */
6365    void sendAfterTextChanged(Editable text) {
6366        if (mListeners != null) {
6367            final ArrayList<TextWatcher> list = mListeners;
6368            final int count = list.size();
6369            for (int i = 0; i < count; i++) {
6370                list.get(i).afterTextChanged(text);
6371            }
6372        }
6373    }
6374
6375    /**
6376     * Not private so it can be called from an inner class without going
6377     * through a thunk.
6378     */
6379    void handleTextChanged(CharSequence buffer, int start,
6380            int before, int after) {
6381        final InputMethodState ims = mInputMethodState;
6382        if (ims == null || ims.mBatchEditNesting == 0) {
6383            updateAfterEdit();
6384        }
6385        if (ims != null) {
6386            ims.mContentChanged = true;
6387            if (ims.mChangedStart < 0) {
6388                ims.mChangedStart = start;
6389                ims.mChangedEnd = start+before;
6390            } else {
6391                ims.mChangedStart = Math.min(ims.mChangedStart, start);
6392                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
6393            }
6394            ims.mChangedDelta += after-before;
6395        }
6396
6397        sendOnTextChanged(buffer, start, before, after);
6398        onTextChanged(buffer, start, before, after);
6399
6400        // Hide the controller if the amount of content changed
6401        if (before != after) {
6402            hideControllers();
6403        }
6404    }
6405
6406    /**
6407     * Not private so it can be called from an inner class without going
6408     * through a thunk.
6409     */
6410    void spanChange(Spanned buf, Object what, int oldStart, int newStart,
6411            int oldEnd, int newEnd) {
6412        // XXX Make the start and end move together if this ends up
6413        // spending too much time invalidating.
6414
6415        boolean selChanged = false;
6416        int newSelStart=-1, newSelEnd=-1;
6417
6418        final InputMethodState ims = mInputMethodState;
6419
6420        if (what == Selection.SELECTION_END) {
6421            mHighlightPathBogus = true;
6422            selChanged = true;
6423            newSelEnd = newStart;
6424
6425            if (!isFocused()) {
6426                mSelectionMoved = true;
6427            }
6428
6429            if (oldStart >= 0 || newStart >= 0) {
6430                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6431                registerForPreDraw();
6432
6433                if (isFocused()) {
6434                    mShowCursor = SystemClock.uptimeMillis();
6435                    makeBlink();
6436                }
6437            }
6438        }
6439
6440        if (what == Selection.SELECTION_START) {
6441            mHighlightPathBogus = true;
6442            selChanged = true;
6443            newSelStart = newStart;
6444
6445            if (!isFocused()) {
6446                mSelectionMoved = true;
6447            }
6448
6449            if (oldStart >= 0 || newStart >= 0) {
6450                int end = Selection.getSelectionEnd(buf);
6451                invalidateCursor(end, oldStart, newStart);
6452            }
6453        }
6454
6455        if (selChanged) {
6456            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6457                if (newSelStart < 0) {
6458                    newSelStart = Selection.getSelectionStart(buf);
6459                }
6460                if (newSelEnd < 0) {
6461                    newSelEnd = Selection.getSelectionEnd(buf);
6462                }
6463                onSelectionChanged(newSelStart, newSelEnd);
6464            }
6465        }
6466
6467        if (what instanceof UpdateAppearance ||
6468            what instanceof ParagraphStyle) {
6469            if (ims == null || ims.mBatchEditNesting == 0) {
6470                invalidate();
6471                mHighlightPathBogus = true;
6472                checkForResize();
6473            } else {
6474                ims.mContentChanged = true;
6475            }
6476        }
6477
6478        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6479            mHighlightPathBogus = true;
6480            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6481                ims.mSelectionModeChanged = true;
6482            }
6483
6484            if (Selection.getSelectionStart(buf) >= 0) {
6485                if (ims == null || ims.mBatchEditNesting == 0) {
6486                    invalidateCursor();
6487                } else {
6488                    ims.mCursorChanged = true;
6489                }
6490            }
6491        }
6492
6493        if (what instanceof ParcelableSpan) {
6494            // If this is a span that can be sent to a remote process,
6495            // the current extract editor would be interested in it.
6496            if (ims != null && ims.mExtracting != null) {
6497                if (ims.mBatchEditNesting != 0) {
6498                    if (oldStart >= 0) {
6499                        if (ims.mChangedStart > oldStart) {
6500                            ims.mChangedStart = oldStart;
6501                        }
6502                        if (ims.mChangedStart > oldEnd) {
6503                            ims.mChangedStart = oldEnd;
6504                        }
6505                    }
6506                    if (newStart >= 0) {
6507                        if (ims.mChangedStart > newStart) {
6508                            ims.mChangedStart = newStart;
6509                        }
6510                        if (ims.mChangedStart > newEnd) {
6511                            ims.mChangedStart = newEnd;
6512                        }
6513                    }
6514                } else {
6515                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
6516                            + oldStart + "-" + oldEnd + ","
6517                            + newStart + "-" + newEnd + what);
6518                    ims.mContentChanged = true;
6519                }
6520            }
6521        }
6522    }
6523
6524    private class ChangeWatcher
6525    implements TextWatcher, SpanWatcher {
6526
6527        private CharSequence mBeforeText;
6528
6529        public void beforeTextChanged(CharSequence buffer, int start,
6530                                      int before, int after) {
6531            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
6532                    + " before=" + before + " after=" + after + ": " + buffer);
6533
6534            if (AccessibilityManager.getInstance(mContext).isEnabled()
6535                    && !isPasswordInputType(mInputType)) {
6536                mBeforeText = buffer.toString();
6537            }
6538
6539            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
6540        }
6541
6542        public void onTextChanged(CharSequence buffer, int start,
6543                                  int before, int after) {
6544            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
6545                    + " before=" + before + " after=" + after + ": " + buffer);
6546            TextView.this.handleTextChanged(buffer, start, before, after);
6547
6548            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
6549                    (isFocused() || isSelected() &&
6550                    isShown())) {
6551                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
6552                mBeforeText = null;
6553            }
6554        }
6555
6556        public void afterTextChanged(Editable buffer) {
6557            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
6558            TextView.this.sendAfterTextChanged(buffer);
6559
6560            if (MetaKeyKeyListener.getMetaState(buffer,
6561                                 MetaKeyKeyListener.META_SELECTING) != 0) {
6562                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
6563            }
6564        }
6565
6566        public void onSpanChanged(Spannable buf,
6567                                  Object what, int s, int e, int st, int en) {
6568            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
6569                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
6570            TextView.this.spanChange(buf, what, s, st, e, en);
6571        }
6572
6573        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
6574            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
6575                    + " what=" + what + ": " + buf);
6576            TextView.this.spanChange(buf, what, -1, s, -1, e);
6577        }
6578
6579        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
6580            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
6581                    + " what=" + what + ": " + buf);
6582            TextView.this.spanChange(buf, what, s, -1, e, -1);
6583        }
6584    }
6585
6586    private void makeBlink() {
6587        if (!mCursorVisible) {
6588            if (mBlink != null) {
6589                mBlink.removeCallbacks(mBlink);
6590            }
6591
6592            return;
6593        }
6594
6595        if (mBlink == null)
6596            mBlink = new Blink(this);
6597
6598        mBlink.removeCallbacks(mBlink);
6599        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
6600    }
6601
6602    /**
6603     * @hide
6604     */
6605    @Override
6606    public void dispatchFinishTemporaryDetach() {
6607        mDispatchTemporaryDetach = true;
6608        super.dispatchFinishTemporaryDetach();
6609        mDispatchTemporaryDetach = false;
6610    }
6611
6612    @Override
6613    public void onStartTemporaryDetach() {
6614        super.onStartTemporaryDetach();
6615        // Only track when onStartTemporaryDetach() is called directly,
6616        // usually because this instance is an editable field in a list
6617        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
6618    }
6619
6620    @Override
6621    public void onFinishTemporaryDetach() {
6622        super.onFinishTemporaryDetach();
6623        // Only track when onStartTemporaryDetach() is called directly,
6624        // usually because this instance is an editable field in a list
6625        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
6626    }
6627
6628    @Override
6629    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
6630        if (mTemporaryDetach) {
6631            // If we are temporarily in the detach state, then do nothing.
6632            super.onFocusChanged(focused, direction, previouslyFocusedRect);
6633            return;
6634        }
6635
6636        mShowCursor = SystemClock.uptimeMillis();
6637
6638        ensureEndedBatchEdit();
6639
6640        if (focused) {
6641            int selStart = getSelectionStart();
6642            int selEnd = getSelectionEnd();
6643
6644            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
6645                // If a tap was used to give focus to that view, move cursor at tap position.
6646                // Has to be done before onTakeFocus, which can be overloaded.
6647                final int lastTapPosition = getLastTapPosition();
6648                if (lastTapPosition >= 0) {
6649                    Selection.setSelection((Spannable) mText, lastTapPosition);
6650                }
6651
6652                if (mMovement != null) {
6653                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
6654                }
6655
6656                if (mSelectAllOnFocus) {
6657                    Selection.setSelection((Spannable) mText, 0, mText.length());
6658                }
6659
6660                // The DecorView does not have focus when the 'Done' ExtractEditText button is
6661                // pressed. Since it is the ViewRoot's mView, it requests focus before
6662                // ExtractEditText clears focus, which gives focus to the ExtractEditText.
6663                // This special case ensure that we keep current selection in that case.
6664                // It would be better to know why the DecorView does not have focus at that time.
6665                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
6666                        selStart >= 0 && selEnd >= 0) {
6667                    /*
6668                     * Someone intentionally set the selection, so let them
6669                     * do whatever it is that they wanted to do instead of
6670                     * the default on-focus behavior.  We reset the selection
6671                     * here instead of just skipping the onTakeFocus() call
6672                     * because some movement methods do something other than
6673                     * just setting the selection in theirs and we still
6674                     * need to go through that path.
6675                     */
6676                    Selection.setSelection((Spannable) mText, selStart, selEnd);
6677                }
6678                mTouchFocusSelected = true;
6679            }
6680
6681            mFrozenWithFocus = false;
6682            mSelectionMoved = false;
6683
6684            if (mText instanceof Spannable) {
6685                Spannable sp = (Spannable) mText;
6686                MetaKeyKeyListener.resetMetaState(sp);
6687            }
6688
6689            makeBlink();
6690
6691            if (mError != null) {
6692                showError();
6693            }
6694        } else {
6695            if (mError != null) {
6696                hideError();
6697            }
6698            // Don't leave us in the middle of a batch edit.
6699            onEndBatchEdit();
6700
6701            hideInsertionPointCursorController();
6702            if (this instanceof ExtractEditText) {
6703                // terminateTextSelectionMode removes selection, which we want to keep when
6704                // ExtractEditText goes out of focus.
6705                final int selStart = getSelectionStart();
6706                final int selEnd = getSelectionEnd();
6707                terminateSelectionActionMode();
6708                Selection.setSelection((Spannable) mText, selStart, selEnd);
6709            } else {
6710                terminateSelectionActionMode();
6711            }
6712
6713            if (mSelectionModifierCursorController != null) {
6714                ((SelectionModifierCursorController) mSelectionModifierCursorController).resetTouchOffsets();
6715            }
6716        }
6717
6718        startStopMarquee(focused);
6719
6720        if (mTransformation != null) {
6721            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
6722        }
6723
6724        super.onFocusChanged(focused, direction, previouslyFocusedRect);
6725    }
6726
6727    private int getLastTapPosition() {
6728        if (mSelectionModifierCursorController != null) {
6729            int lastTapPosition = ((SelectionModifierCursorController)
6730                    mSelectionModifierCursorController).getMinTouchOffset();
6731            if (lastTapPosition >= 0) {
6732                // Safety check, should not be possible.
6733                if (lastTapPosition > mText.length()) {
6734                    Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
6735                            + mText.length() + ")");
6736                    lastTapPosition = mText.length();
6737                }
6738                return lastTapPosition;
6739            }
6740        }
6741
6742        return -1;
6743    }
6744
6745    @Override
6746    public void onWindowFocusChanged(boolean hasWindowFocus) {
6747        super.onWindowFocusChanged(hasWindowFocus);
6748
6749        if (hasWindowFocus) {
6750            if (mBlink != null) {
6751                mBlink.uncancel();
6752
6753                if (isFocused()) {
6754                    mShowCursor = SystemClock.uptimeMillis();
6755                    makeBlink();
6756                }
6757            }
6758        } else {
6759            if (mBlink != null) {
6760                mBlink.cancel();
6761            }
6762            // Don't leave us in the middle of a batch edit.
6763            onEndBatchEdit();
6764            if (mInputContentType != null) {
6765                mInputContentType.enterDown = false;
6766            }
6767            hideControllers();
6768        }
6769
6770        startStopMarquee(hasWindowFocus);
6771    }
6772
6773    @Override
6774    protected void onVisibilityChanged(View changedView, int visibility) {
6775        super.onVisibilityChanged(changedView, visibility);
6776        if (visibility != VISIBLE) {
6777            hideControllers();
6778        }
6779    }
6780
6781    /**
6782     * Use {@link BaseInputConnection#removeComposingSpans
6783     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
6784     * state from this text view.
6785     */
6786    public void clearComposingText() {
6787        if (mText instanceof Spannable) {
6788            BaseInputConnection.removeComposingSpans((Spannable)mText);
6789        }
6790    }
6791
6792    @Override
6793    public void setSelected(boolean selected) {
6794        boolean wasSelected = isSelected();
6795
6796        super.setSelected(selected);
6797
6798        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6799            if (selected) {
6800                startMarquee();
6801            } else {
6802                stopMarquee();
6803            }
6804        }
6805    }
6806
6807    private void onTapUpEvent(int prevStart, int prevEnd) {
6808        final int start = getSelectionStart();
6809        final int end = getSelectionEnd();
6810
6811        if (start == end) {
6812            if (start >= prevStart && start < prevEnd) {
6813                // Restore previous selection
6814                Selection.setSelection((Spannable)mText, prevStart, prevEnd);
6815
6816                if (mSelectionModifierCursorController != null &&
6817                        !mSelectionModifierCursorController.isShowing()) {
6818                    // If the anchors aren't showing, revive them.
6819                    mSelectionModifierCursorController.show();
6820                } else {
6821                    // Tapping inside the selection displays the cut/copy/paste context menu
6822                    // as long as the anchors are already showing.
6823                    showContextMenu();
6824                }
6825                return;
6826            } else {
6827                // Tapping outside stops selection mode, if any
6828                stopSelectionActionMode();
6829
6830                if (mInsertionPointCursorController != null && mText.length() > 0) {
6831                    mInsertionPointCursorController.show();
6832                }
6833            }
6834        } else if (hasSelection() && mSelectionModifierCursorController != null) {
6835            mSelectionModifierCursorController.show();
6836        }
6837    }
6838
6839    class CommitSelectionReceiver extends ResultReceiver {
6840        private final int mPrevStart, mPrevEnd;
6841
6842        public CommitSelectionReceiver(int prevStart, int prevEnd) {
6843            super(getHandler());
6844            mPrevStart = prevStart;
6845            mPrevEnd = prevEnd;
6846        }
6847
6848        @Override
6849        protected void onReceiveResult(int resultCode, Bundle resultData) {
6850            // If this tap was actually used to show the IMM, leave cursor or selection unchanged
6851            // by restoring its previous position.
6852            if (resultCode == InputMethodManager.RESULT_SHOWN) {
6853                final int len = mText.length();
6854                int start = Math.min(len, mPrevStart);
6855                int end = Math.min(len, mPrevEnd);
6856                Selection.setSelection((Spannable)mText, start, end);
6857
6858                if (hasSelection()) {
6859                    startSelectionActionMode();
6860                }
6861            }
6862        }
6863    }
6864
6865    @Override
6866    public boolean onTouchEvent(MotionEvent event) {
6867        final int action = event.getActionMasked();
6868        if (action == MotionEvent.ACTION_DOWN) {
6869            if (mInsertionPointCursorController != null) {
6870                mInsertionPointCursorController.onTouchEvent(event);
6871            }
6872            if (mSelectionModifierCursorController != null) {
6873                mSelectionModifierCursorController.onTouchEvent(event);
6874            }
6875
6876            // Reset this state; it will be re-set if super.onTouchEvent
6877            // causes focus to move to the view.
6878            mTouchFocusSelected = false;
6879            mScrolled = false;
6880        }
6881
6882        final boolean superResult = super.onTouchEvent(event);
6883
6884        /*
6885         * Don't handle the release after a long press, because it will
6886         * move the selection away from whatever the menu action was
6887         * trying to affect.
6888         */
6889        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
6890            mEatTouchRelease = false;
6891            return superResult;
6892        }
6893
6894        if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
6895            if (mInsertionPointCursorController != null) {
6896                mInsertionPointCursorController.onTouchEvent(event);
6897            }
6898            if (mSelectionModifierCursorController != null) {
6899                mSelectionModifierCursorController.onTouchEvent(event);
6900            }
6901
6902            boolean handled = false;
6903
6904            // Save previous selection, in case this event is used to show the IME.
6905            int oldSelStart = getSelectionStart();
6906            int oldSelEnd = getSelectionEnd();
6907
6908            final int oldScrollX = mScrollX;
6909            final int oldScrollY = mScrollY;
6910
6911            if (mMovement != null) {
6912                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
6913            }
6914
6915            if (isTextEditable()) {
6916                if (mScrollX != oldScrollX || mScrollY != oldScrollY) {
6917                    // Hide insertion anchor while scrolling. Leave selection.
6918                    hideInsertionPointCursorController();
6919                    if (mSelectionModifierCursorController != null &&
6920                            mSelectionModifierCursorController.isShowing()) {
6921                        mSelectionModifierCursorController.updatePosition();
6922                    }
6923                }
6924                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
6925                    InputMethodManager imm = (InputMethodManager)
6926                          getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
6927
6928                    CommitSelectionReceiver csr = null;
6929                    if (getSelectionStart() != oldSelStart || getSelectionEnd() != oldSelEnd ||
6930                            didTouchFocusSelect()) {
6931                        csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd);
6932                    }
6933
6934                    handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
6935
6936                    // Cannot be done by CommitSelectionReceiver, which might not always be called,
6937                    // for instance when dealing with an ExtractEditText.
6938                    onTapUpEvent(oldSelStart, oldSelEnd);
6939                }
6940            }
6941
6942            if (handled) {
6943                return true;
6944            }
6945        }
6946
6947        return superResult;
6948    }
6949
6950    private void prepareCursorControllers() {
6951        boolean windowSupportsHandles = false;
6952
6953        ViewGroup.LayoutParams params = getRootView().getLayoutParams();
6954        if (params instanceof WindowManager.LayoutParams) {
6955            WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
6956            windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
6957                    || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
6958        }
6959
6960        // TODO Add an extra android:cursorController flag to disable the controller?
6961        if (windowSupportsHandles && mCursorVisible && mLayout != null) {
6962            if (mInsertionPointCursorController == null) {
6963                mInsertionPointCursorController = new InsertionPointCursorController();
6964            }
6965        } else {
6966            mInsertionPointCursorController = null;
6967        }
6968
6969        if (windowSupportsHandles && textCanBeSelected() && mLayout != null) {
6970            if (mSelectionModifierCursorController == null) {
6971                mSelectionModifierCursorController = new SelectionModifierCursorController();
6972            }
6973        } else {
6974            // Stop selection mode if the controller becomes unavailable.
6975            stopSelectionActionMode();
6976            mSelectionModifierCursorController = null;
6977        }
6978    }
6979
6980    /**
6981     * @return True iff this TextView contains a text that can be edited.
6982     */
6983    private boolean isTextEditable() {
6984        return mText instanceof Editable && onCheckIsTextEditor();
6985    }
6986
6987    /**
6988     * Returns true, only while processing a touch gesture, if the initial
6989     * touch down event caused focus to move to the text view and as a result
6990     * its selection changed.  Only valid while processing the touch gesture
6991     * of interest.
6992     */
6993    public boolean didTouchFocusSelect() {
6994        return mTouchFocusSelected;
6995    }
6996
6997    @Override
6998    public void cancelLongPress() {
6999        super.cancelLongPress();
7000        mScrolled = true;
7001    }
7002
7003    @Override
7004    public boolean onTrackballEvent(MotionEvent event) {
7005        if (mMovement != null && mText instanceof Spannable &&
7006            mLayout != null) {
7007            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7008                return true;
7009            }
7010        }
7011
7012        return super.onTrackballEvent(event);
7013    }
7014
7015    public void setScroller(Scroller s) {
7016        mScroller = s;
7017    }
7018
7019    private static class Blink extends Handler implements Runnable {
7020        private final WeakReference<TextView> mView;
7021        private boolean mCancelled;
7022
7023        public Blink(TextView v) {
7024            mView = new WeakReference<TextView>(v);
7025        }
7026
7027        public void run() {
7028            if (mCancelled) {
7029                return;
7030            }
7031
7032            removeCallbacks(Blink.this);
7033
7034            TextView tv = mView.get();
7035
7036            if (tv != null && tv.isFocused()) {
7037                int st = tv.getSelectionStart();
7038                int en = tv.getSelectionEnd();
7039
7040                if (st == en && st >= 0 && en >= 0) {
7041                    if (tv.mLayout != null) {
7042                        tv.invalidateCursorPath();
7043                    }
7044
7045                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
7046                }
7047            }
7048        }
7049
7050        void cancel() {
7051            if (!mCancelled) {
7052                removeCallbacks(Blink.this);
7053                mCancelled = true;
7054            }
7055        }
7056
7057        void uncancel() {
7058            mCancelled = false;
7059        }
7060    }
7061
7062    @Override
7063    protected float getLeftFadingEdgeStrength() {
7064        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7065        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7066            if (mMarquee != null && !mMarquee.isStopped()) {
7067                final Marquee marquee = mMarquee;
7068                if (marquee.shouldDrawLeftFade()) {
7069                    return marquee.mScroll / getHorizontalFadingEdgeLength();
7070                } else {
7071                    return 0.0f;
7072                }
7073            } else if (getLineCount() == 1) {
7074                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7075                    case Gravity.LEFT:
7076                        return 0.0f;
7077                    case Gravity.RIGHT:
7078                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
7079                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
7080                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7081                    case Gravity.CENTER_HORIZONTAL:
7082                        return 0.0f;
7083                }
7084            }
7085        }
7086        return super.getLeftFadingEdgeStrength();
7087    }
7088
7089    @Override
7090    protected float getRightFadingEdgeStrength() {
7091        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7092        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7093            if (mMarquee != null && !mMarquee.isStopped()) {
7094                final Marquee marquee = mMarquee;
7095                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
7096            } else if (getLineCount() == 1) {
7097                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7098                    case Gravity.LEFT:
7099                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7100                                getCompoundPaddingRight();
7101                        final float lineWidth = mLayout.getLineWidth(0);
7102                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
7103                    case Gravity.RIGHT:
7104                        return 0.0f;
7105                    case Gravity.CENTER_HORIZONTAL:
7106                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7107                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7108                                getHorizontalFadingEdgeLength();
7109                }
7110            }
7111        }
7112        return super.getRightFadingEdgeStrength();
7113    }
7114
7115    @Override
7116    protected int computeHorizontalScrollRange() {
7117        if (mLayout != null) {
7118            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7119                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7120        }
7121
7122        return super.computeHorizontalScrollRange();
7123    }
7124
7125    @Override
7126    protected int computeVerticalScrollRange() {
7127        if (mLayout != null)
7128            return mLayout.getHeight();
7129
7130        return super.computeVerticalScrollRange();
7131    }
7132
7133    @Override
7134    protected int computeVerticalScrollExtent() {
7135        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7136    }
7137
7138    public enum BufferType {
7139        NORMAL, SPANNABLE, EDITABLE,
7140    }
7141
7142    /**
7143     * Returns the TextView_textColor attribute from the
7144     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7145     * from the TextView_textAppearance attribute, if TextView_textColor
7146     * was not set directly.
7147     */
7148    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7149        ColorStateList colors;
7150        colors = attrs.getColorStateList(com.android.internal.R.styleable.
7151                                         TextView_textColor);
7152
7153        if (colors == null) {
7154            int ap = attrs.getResourceId(com.android.internal.R.styleable.
7155                                         TextView_textAppearance, -1);
7156            if (ap != -1) {
7157                TypedArray appearance;
7158                appearance = context.obtainStyledAttributes(ap,
7159                                            com.android.internal.R.styleable.TextAppearance);
7160                colors = appearance.getColorStateList(com.android.internal.R.styleable.
7161                                                  TextAppearance_textColor);
7162                appearance.recycle();
7163            }
7164        }
7165
7166        return colors;
7167    }
7168
7169    /**
7170     * Returns the default color from the TextView_textColor attribute
7171     * from the AttributeSet, if set, or the default color from the
7172     * TextAppearance_textColor from the TextView_textAppearance attribute,
7173     * if TextView_textColor was not set directly.
7174     */
7175    public static int getTextColor(Context context,
7176                                   TypedArray attrs,
7177                                   int def) {
7178        ColorStateList colors = getTextColors(context, attrs);
7179
7180        if (colors == null) {
7181            return def;
7182        } else {
7183            return colors.getDefaultColor();
7184        }
7185    }
7186
7187    @Override
7188    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
7189        switch (keyCode) {
7190        case KeyEvent.KEYCODE_A:
7191            if (canSelectText()) {
7192                return onTextContextMenuItem(ID_SELECT_ALL);
7193            }
7194
7195            break;
7196
7197        case KeyEvent.KEYCODE_X:
7198            if (canCut()) {
7199                return onTextContextMenuItem(ID_CUT);
7200            }
7201
7202            break;
7203
7204        case KeyEvent.KEYCODE_C:
7205            if (canCopy()) {
7206                return onTextContextMenuItem(ID_COPY);
7207            }
7208
7209            break;
7210
7211        case KeyEvent.KEYCODE_V:
7212            if (canPaste()) {
7213                return onTextContextMenuItem(ID_PASTE);
7214            }
7215
7216            break;
7217        }
7218
7219        return super.onKeyShortcut(keyCode, event);
7220    }
7221
7222    private boolean canSelectText() {
7223        return textCanBeSelected() && mText.length() != 0;
7224    }
7225
7226    private boolean textCanBeSelected() {
7227        // prepareCursorController() relies on this method.
7228        // If you change this condition, make sure prepareCursorController is called anywhere
7229        // the value of this condition might be changed.
7230        return (mText instanceof Spannable &&
7231                mMovement != null &&
7232                mMovement.canSelectArbitrarily());
7233    }
7234
7235    private boolean canCut() {
7236        if (mTransformation instanceof PasswordTransformationMethod) {
7237            return false;
7238        }
7239
7240        if (mText.length() > 0 && hasSelection()) {
7241            if (mText instanceof Editable && mInput != null) {
7242                return true;
7243            }
7244        }
7245
7246        return false;
7247    }
7248
7249    private boolean canCopy() {
7250        if (mTransformation instanceof PasswordTransformationMethod) {
7251            return false;
7252        }
7253
7254        if (mText.length() > 0 && hasSelection()) {
7255            return true;
7256        }
7257
7258        return false;
7259    }
7260
7261    private boolean canPaste() {
7262        return (mText instanceof Editable &&
7263                mInput != null &&
7264                getSelectionStart() >= 0 &&
7265                getSelectionEnd() >= 0 &&
7266                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
7267                hasPrimaryClip());
7268    }
7269
7270    /**
7271     * Returns the offsets delimiting the 'word' located at position offset.
7272     *
7273     * @param offset An offset in the text.
7274     * @return The offsets for the start and end of the word located at <code>offset</code>.
7275     * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
7276     * Returns a negative value if no valid word was found.
7277     */
7278    private long getWordLimitsAt(int offset) {
7279        /*
7280         * Quick return if the input type is one where adding words
7281         * to the dictionary doesn't make any sense.
7282         */
7283        int klass = mInputType & InputType.TYPE_MASK_CLASS;
7284        if (klass == InputType.TYPE_CLASS_NUMBER ||
7285            klass == InputType.TYPE_CLASS_PHONE ||
7286            klass == InputType.TYPE_CLASS_DATETIME) {
7287            return -1;
7288        }
7289
7290        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
7291        if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
7292            variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
7293            variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
7294            variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
7295            variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
7296            return -1;
7297        }
7298
7299        int len = mText.length();
7300        int end = Math.min(offset, len);
7301
7302        if (end < 0) {
7303            return -1;
7304        }
7305
7306        int start = end;
7307
7308        for (; start > 0; start--) {
7309            char c = mTransformed.charAt(start - 1);
7310            int type = Character.getType(c);
7311
7312            // Cases where the text ends with a '.' and we select from the end of the line (right
7313            // after the dot), or when we select from the space character in "aaaa, bbbb".
7314            if (start == end && type == Character.OTHER_PUNCTUATION) continue;
7315
7316            if (c != '\'' &&
7317                type != Character.UPPERCASE_LETTER &&
7318                type != Character.LOWERCASE_LETTER &&
7319                type != Character.TITLECASE_LETTER &&
7320                type != Character.MODIFIER_LETTER &&
7321                type != Character.DECIMAL_DIGIT_NUMBER) {
7322                break;
7323            }
7324        }
7325
7326        for (; end < len; end++) {
7327            char c = mTransformed.charAt(end);
7328            int type = Character.getType(c);
7329
7330            if (c != '\'' &&
7331                type != Character.UPPERCASE_LETTER &&
7332                type != Character.LOWERCASE_LETTER &&
7333                type != Character.TITLECASE_LETTER &&
7334                type != Character.MODIFIER_LETTER &&
7335                type != Character.DECIMAL_DIGIT_NUMBER) {
7336                break;
7337            }
7338        }
7339
7340        if (start == end) {
7341            return -1;
7342        }
7343
7344        if (end - start > 48) {
7345            return -1;
7346        }
7347
7348        boolean hasLetter = false;
7349        for (int i = start; i < end; i++) {
7350            if (Character.isLetter(mTransformed.charAt(i))) {
7351                hasLetter = true;
7352                break;
7353            }
7354        }
7355
7356        if (!hasLetter) {
7357            return -1;
7358        }
7359
7360        // Two ints packed in a long
7361        return packRangeInLong(start, end);
7362    }
7363
7364    private static long packRangeInLong(int start, int end) {
7365        return (((long) start) << 32) | end;
7366    }
7367
7368    private static int extractRangeStartFromLong(long range) {
7369        return (int) (range >>> 32);
7370    }
7371
7372    private static int extractRangeEndFromLong(long range) {
7373        return (int) (range & 0x00000000FFFFFFFFL);
7374    }
7375
7376    private void selectCurrentWord() {
7377        // In case selection mode is started after an orientation change or after a select all,
7378        // use the current selection instead of creating one
7379        if (hasSelection()) {
7380            return;
7381        }
7382
7383        int minOffset, maxOffset;
7384
7385        if (mContextMenuTriggeredByKey) {
7386            minOffset = getSelectionStart();
7387            maxOffset = getSelectionEnd();
7388        } else {
7389            // selectionModifierCursorController is not null at that point
7390            SelectionModifierCursorController selectionModifierCursorController =
7391                ((SelectionModifierCursorController) mSelectionModifierCursorController);
7392            minOffset = selectionModifierCursorController.getMinTouchOffset();
7393            maxOffset = selectionModifierCursorController.getMaxTouchOffset();
7394        }
7395
7396        int selectionStart, selectionEnd;
7397
7398        long wordLimits = getWordLimitsAt(minOffset);
7399        if (wordLimits >= 0) {
7400            selectionStart = extractRangeStartFromLong(wordLimits);
7401        } else {
7402            selectionStart = Math.max(minOffset - 5, 0);
7403        }
7404
7405        wordLimits = getWordLimitsAt(maxOffset);
7406        if (wordLimits >= 0) {
7407            selectionEnd = extractRangeEndFromLong(wordLimits);
7408        } else {
7409            selectionEnd = Math.min(maxOffset + 5, mText.length());
7410        }
7411
7412        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
7413    }
7414
7415    @Override
7416    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
7417        if (!isShown()) {
7418            return false;
7419        }
7420
7421        final boolean isPassword = isPasswordInputType(mInputType);
7422
7423        if (!isPassword) {
7424            CharSequence text = getText();
7425            if (TextUtils.isEmpty(text)) {
7426                text = getHint();
7427            }
7428            if (!TextUtils.isEmpty(text)) {
7429                if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
7430                    text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
7431                }
7432                event.getText().add(text);
7433            }
7434        } else {
7435            event.setPassword(isPassword);
7436        }
7437        return false;
7438    }
7439
7440    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7441            int fromIndex, int removedCount, int addedCount) {
7442        AccessibilityEvent event =
7443            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7444        event.setFromIndex(fromIndex);
7445        event.setRemovedCount(removedCount);
7446        event.setAddedCount(addedCount);
7447        event.setBeforeText(beforeText);
7448        sendAccessibilityEventUnchecked(event);
7449    }
7450
7451    @Override
7452    protected void onCreateContextMenu(ContextMenu menu) {
7453        super.onCreateContextMenu(menu);
7454        boolean added = false;
7455        mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
7456        // Problem with context menu on long press: the menu appears while the key in down and when
7457        // the key is released, the view does not receive the key_up event. This ensures that the
7458        // state is reset whenever the context menu action is displayed.
7459        // mContextMenuTriggeredByKey saved that state so that it is available in
7460        // onTextContextMenuItem. We cannot simply clear these flags in onTextContextMenuItem since
7461        // it may not be called (if the user/ discards the context menu with the back key).
7462        mDPadCenterIsDown = mEnterKeyIsDown = false;
7463
7464        MenuHandler handler = new MenuHandler();
7465
7466        if (mText instanceof Spanned) {
7467            int selStart = getSelectionStart();
7468            int selEnd = getSelectionEnd();
7469
7470            int min = Math.min(selStart, selEnd);
7471            int max = Math.max(selStart, selEnd);
7472
7473            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7474                                                        URLSpan.class);
7475            if (urls.length == 1) {
7476                menu.add(0, ID_COPY_URL, 0,
7477                         com.android.internal.R.string.copyUrl).
7478                            setOnMenuItemClickListener(handler);
7479
7480                added = true;
7481            }
7482        }
7483
7484        // The context menu is not empty, which will prevent the selection mode from starting.
7485        // Add a entry to start it in the context menu.
7486        // TODO Does not handle the case where a subclass does not call super.thisMethod or
7487        // populates the menu AFTER this call.
7488        if (menu.size() > 0) {
7489            menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode).
7490            setOnMenuItemClickListener(handler);
7491            added = true;
7492        }
7493
7494        if (added) {
7495            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7496        }
7497    }
7498
7499    /**
7500     * Returns whether this text view is a current input method target.  The
7501     * default implementation just checks with {@link InputMethodManager}.
7502     */
7503    public boolean isInputMethodTarget() {
7504        InputMethodManager imm = InputMethodManager.peekInstance();
7505        return imm != null && imm.isActive(this);
7506    }
7507
7508    // Selection context mode
7509    private static final int ID_SELECT_ALL = android.R.id.selectAll;
7510    private static final int ID_CUT = android.R.id.cut;
7511    private static final int ID_COPY = android.R.id.copy;
7512    private static final int ID_PASTE = android.R.id.paste;
7513    // Context menu entries
7514    private static final int ID_COPY_URL = android.R.id.copyUrl;
7515    private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
7516
7517    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
7518        public boolean onMenuItemClick(MenuItem item) {
7519            return onTextContextMenuItem(item.getItemId());
7520        }
7521    }
7522
7523    /**
7524     * Called when a context menu option for the text view is selected.  Currently
7525     * this will be {@link android.R.id#copyUrl} or {@link android.R.id#selectTextMode}.
7526     */
7527    public boolean onTextContextMenuItem(int id) {
7528        int min = 0;
7529        int max = mText.length();
7530
7531        if (isFocused()) {
7532            final int selStart = getSelectionStart();
7533            final int selEnd = getSelectionEnd();
7534
7535            min = Math.max(0, Math.min(selStart, selEnd));
7536            max = Math.max(0, Math.max(selStart, selEnd));
7537        }
7538
7539        ClipboardManager clipboard = (ClipboardManager)getContext()
7540                .getSystemService(Context.CLIPBOARD_SERVICE);
7541
7542        switch (id) {
7543            case ID_COPY_URL:
7544                URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
7545                if (urls.length >= 1) {
7546                    ClipData clip = null;
7547                    for (int i=0; i<urls.length; i++) {
7548                        Uri uri = Uri.parse(urls[0].getURL());
7549                        if (clip == null) {
7550                            clip = ClipData.newRawUri(null, null, uri);
7551                        } else {
7552                            clip.addItem(new ClipData.Item(uri));
7553                        }
7554                    }
7555                    if (clip != null) {
7556                        clipboard.setPrimaryClip(clip);
7557                    }
7558                }
7559                return true;
7560
7561            case ID_SELECTION_MODE:
7562                startSelectionActionMode();
7563                return true;
7564            }
7565
7566        return false;
7567    }
7568
7569    /**
7570     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
7571     * by [min, max] when replacing this region by paste.
7572     */
7573    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
7574        // Paste adds/removes spaces before or after insertion as needed.
7575        if (Character.isSpaceChar(paste.charAt(0))) {
7576            if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
7577                // Two spaces at beginning of paste: remove one
7578                final int originalLength = mText.length();
7579                ((Editable) mText).replace(min - 1, min, "");
7580                // Due to filters, there is no garantee that exactly one character was
7581                // removed. Count instead.
7582                final int delta = mText.length() - originalLength;
7583                min += delta;
7584                max += delta;
7585            }
7586        } else {
7587            if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
7588                // No space at beginning of paste: add one
7589                final int originalLength = mText.length();
7590                ((Editable) mText).replace(min, min, " ");
7591                // Taking possible filters into account as above.
7592                final int delta = mText.length() - originalLength;
7593                min += delta;
7594                max += delta;
7595            }
7596        }
7597
7598        if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
7599            if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
7600                // Two spaces at end of paste: remove one
7601                ((Editable) mText).replace(max, max + 1, "");
7602            }
7603        } else {
7604            if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
7605                // No space at end of paste: add one
7606                ((Editable) mText).replace(max, max, " ");
7607            }
7608        }
7609        return packRangeInLong(min, max);
7610    }
7611
7612    @Override
7613    public boolean performLongClick() {
7614        if (super.performLongClick()) {
7615            mEatTouchRelease = true;
7616            return true;
7617        }
7618
7619        if (startSelectionActionMode()) {
7620            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
7621            mEatTouchRelease = true;
7622            return true;
7623        }
7624
7625        return false;
7626    }
7627
7628    private boolean touchPositionIsInSelection() {
7629        int selectionStart = getSelectionStart();
7630        int selectionEnd = getSelectionEnd();
7631
7632        if (selectionStart == selectionEnd) {
7633            return false;
7634        }
7635
7636        if (selectionStart > selectionEnd) {
7637            int tmp = selectionStart;
7638            selectionStart = selectionEnd;
7639            selectionEnd = tmp;
7640            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
7641        }
7642
7643        SelectionModifierCursorController selectionModifierCursorController =
7644            ((SelectionModifierCursorController) mSelectionModifierCursorController);
7645        int minOffset = selectionModifierCursorController.getMinTouchOffset();
7646        int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
7647
7648        return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
7649    }
7650
7651    /**
7652     * Provides the callback used to start a selection action mode.
7653     *
7654     * @return A callback instance that will be used to start selection mode, or null if selection
7655     * mode is not available.
7656     */
7657    private ActionMode.Callback getActionModeCallback() {
7658        // Long press in the current selection.
7659        // Should initiate a drag. Return false, to rely on context menu for now.
7660        if (canSelectText() && !touchPositionIsInSelection()) {
7661            return new SelectionActionModeCallback();
7662        }
7663        return null;
7664    }
7665
7666    /**
7667     *
7668     * @return true if the selection mode was actually started.
7669     */
7670    private boolean startSelectionActionMode() {
7671        if (mSelectionActionMode != null) {
7672            // Selection action mode is already started
7673            return false;
7674        }
7675
7676        ActionMode.Callback actionModeCallback = getActionModeCallback();
7677        if (actionModeCallback != null) {
7678            mSelectionActionMode = startActionMode(actionModeCallback);
7679            return mSelectionActionMode != null;
7680        }
7681
7682        return false;
7683    }
7684
7685    /**
7686     * Same as {@link #stopSelectionActionMode()}, except that there is no cursor controller
7687     * fade out animation. Needed since the drawable and their alpha values are shared by all
7688     * TextViews. Switching from one TextView to another would fade the cursor controllers in the
7689     * new one otherwise.
7690     */
7691    private void terminateSelectionActionMode() {
7692        stopSelectionActionMode();
7693        if (mSelectionModifierCursorController != null) {
7694            SelectionModifierCursorController selectionModifierCursorController =
7695                (SelectionModifierCursorController) mSelectionModifierCursorController;
7696            selectionModifierCursorController.cancelFadeOutAnimation();
7697        }
7698    }
7699
7700    private void stopSelectionActionMode() {
7701        if (mSelectionActionMode != null) {
7702            mSelectionActionMode.finish();
7703        }
7704    }
7705
7706    private class SelectionActionModeCallback implements ActionMode.Callback {
7707
7708        @Override
7709        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
7710            if (mSelectionModifierCursorController == null) {
7711                Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
7712                return false;
7713            }
7714
7715            if (!requestFocus()) {
7716                return false;
7717            }
7718
7719            TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
7720
7721            mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
7722            mode.setSubtitle(null);
7723
7724            selectCurrentWord();
7725
7726            boolean atLeastOne = false;
7727
7728            if (canSelectText()) {
7729                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
7730                    setIcon(com.android.internal.R.drawable.ic_menu_select_all).
7731                    setAlphabeticShortcut('a').
7732                    setShowAsAction(
7733                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
7734                atLeastOne = true;
7735            }
7736
7737            if (canCut()) {
7738                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
7739                    setIcon(styledAttributes.getResourceId(R.styleable.Theme_actionModeCutDrawable, 0)).
7740                    setAlphabeticShortcut('x').
7741                    setShowAsAction(
7742                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
7743                atLeastOne = true;
7744            }
7745
7746            if (canCopy()) {
7747                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
7748                    setIcon(styledAttributes.getResourceId(R.styleable.Theme_actionModeCopyDrawable, 0)).
7749                    setAlphabeticShortcut('c').
7750                    setShowAsAction(
7751                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
7752                atLeastOne = true;
7753            }
7754
7755            if (canPaste()) {
7756                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
7757                        setIcon(styledAttributes.getResourceId(R.styleable.Theme_actionModePasteDrawable, 0)).
7758                        setAlphabeticShortcut('v').
7759                        setShowAsAction(
7760                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
7761                atLeastOne = true;
7762            }
7763
7764            styledAttributes.recycle();
7765
7766            if (atLeastOne) {
7767                mSelectionModifierCursorController.show();
7768                return true;
7769            } else {
7770                return false;
7771            }
7772        }
7773
7774        @Override
7775        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
7776            return true;
7777        }
7778
7779        @Override
7780        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
7781            final int itemId = item.getItemId();
7782
7783            if (itemId == ID_SELECT_ALL) {
7784                Selection.setSelection((Spannable) mText, 0, mText.length());
7785                // Update controller positions after selection change.
7786                if (mSelectionModifierCursorController != null) {
7787                    mSelectionModifierCursorController.show();
7788                }
7789                return true;
7790            }
7791
7792            ClipboardManager clipboard = (ClipboardManager) getContext().
7793                    getSystemService(Context.CLIPBOARD_SERVICE);
7794
7795            int min = 0;
7796            int max = mText.length();
7797
7798            if (isFocused()) {
7799                final int selStart = getSelectionStart();
7800                final int selEnd = getSelectionEnd();
7801
7802                min = Math.max(0, Math.min(selStart, selEnd));
7803                max = Math.max(0, Math.max(selStart, selEnd));
7804            }
7805
7806            switch (item.getItemId()) {
7807                case ID_PASTE:
7808                    ClipData clip = clipboard.getPrimaryClip();
7809                    if (clip != null) {
7810                        boolean didfirst = false;
7811                        for (int i=0; i<clip.getItemCount(); i++) {
7812                            CharSequence paste = clip.getItem(i).coerceToText(getContext());
7813                            if (paste != null) {
7814                                if (!didfirst) {
7815                                    long minMax = prepareSpacesAroundPaste(min, max, paste);
7816                                    min = extractRangeStartFromLong(minMax);
7817                                    max = extractRangeEndFromLong(minMax);
7818                                    Selection.setSelection((Spannable) mText, max);
7819                                    ((Editable) mText).replace(min, max, paste);
7820                                } else {
7821                                    ((Editable) mText).insert(getSelectionEnd(), "\n");
7822                                    ((Editable) mText).insert(getSelectionEnd(), paste);
7823                                }
7824                            }
7825                        }
7826                        stopSelectionActionMode();
7827                    }
7828                    return true;
7829
7830                case ID_CUT:
7831                    clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
7832                            mTransformed.subSequence(min, max)));
7833                    ((Editable) mText).delete(min, max);
7834                    stopSelectionActionMode();
7835                    return true;
7836
7837                case ID_COPY:
7838                    clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
7839                            mTransformed.subSequence(min, max)));
7840                    stopSelectionActionMode();
7841                    return true;
7842            }
7843
7844            return false;
7845        }
7846
7847        @Override
7848        public void onDestroyActionMode(ActionMode mode) {
7849            Selection.setSelection((Spannable) mText, getSelectionStart());
7850            if (mSelectionModifierCursorController != null) {
7851                mSelectionModifierCursorController.hide();
7852            }
7853            mSelectionActionMode = null;
7854        }
7855    }
7856
7857    /**
7858     * A CursorController instance can be used to control a cursor in the text.
7859     * It is not used outside of {@link TextView}.
7860     * @hide
7861     */
7862    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
7863        /**
7864         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
7865         * See also {@link #hide()}.
7866         */
7867        public void show();
7868
7869        /**
7870         * Hide the cursor controller from screen.
7871         * See also {@link #show()}.
7872         */
7873        public void hide();
7874
7875        /**
7876         * @return true if the CursorController is currently visible
7877         */
7878        public boolean isShowing();
7879
7880        /**
7881         * Update the controller's position.
7882         */
7883        public void updatePosition(HandleView handle, int x, int y);
7884
7885        public void updatePosition();
7886
7887        /**
7888         * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
7889         * a chance to become active and/or visible.
7890         * @param event The touch event
7891         */
7892        public boolean onTouchEvent(MotionEvent event);
7893    }
7894
7895    private class HandleView extends View {
7896        private boolean mPositionOnTop = false;
7897        private Drawable mDrawable;
7898        private PopupWindow mContainer;
7899        private int mPositionX;
7900        private int mPositionY;
7901        private CursorController mController;
7902        private boolean mIsDragging;
7903        private float mTouchToWindowOffsetX;
7904        private float mTouchToWindowOffsetY;
7905        private float mHotspotX;
7906        private float mHotspotY;
7907        private int mHeight;
7908        private float mTouchOffsetY;
7909        private int mLastParentX;
7910        private int mLastParentY;
7911
7912        public static final int LEFT = 0;
7913        public static final int CENTER = 1;
7914        public static final int RIGHT = 2;
7915
7916        public HandleView(CursorController controller, int pos) {
7917            super(TextView.this.mContext);
7918            mController = controller;
7919            mContainer = new PopupWindow(TextView.this.mContext, null,
7920                    com.android.internal.R.attr.textSelectHandleWindowStyle);
7921            mContainer.setSplitTouchEnabled(true);
7922            mContainer.setClippingEnabled(false);
7923            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
7924
7925            setOrientation(pos);
7926        }
7927
7928        public void setOrientation(int pos) {
7929            int handleWidth;
7930            switch (pos) {
7931            case LEFT: {
7932                if (mSelectHandleLeft == null) {
7933                    mSelectHandleLeft = mContext.getResources().getDrawable(
7934                            mTextSelectHandleLeftRes);
7935                }
7936                mDrawable = mSelectHandleLeft;
7937                handleWidth = mDrawable.getIntrinsicWidth();
7938                mHotspotX = handleWidth / 4 * 3;
7939                break;
7940            }
7941
7942            case RIGHT: {
7943                if (mSelectHandleRight == null) {
7944                    mSelectHandleRight = mContext.getResources().getDrawable(
7945                            mTextSelectHandleRightRes);
7946                }
7947                mDrawable = mSelectHandleRight;
7948                handleWidth = mDrawable.getIntrinsicWidth();
7949                mHotspotX = handleWidth / 4;
7950                break;
7951            }
7952
7953            case CENTER:
7954            default: {
7955                if (mSelectHandleCenter == null) {
7956                    mSelectHandleCenter = mContext.getResources().getDrawable(
7957                            mTextSelectHandleRes);
7958                }
7959                mDrawable = mSelectHandleCenter;
7960                handleWidth = mDrawable.getIntrinsicWidth();
7961                mHotspotX = handleWidth / 2;
7962                break;
7963            }
7964            }
7965
7966            final int handleHeight = mDrawable.getIntrinsicHeight();
7967
7968            mTouchOffsetY = -handleHeight * 0.3f;
7969            mHotspotY = 0;
7970            mHeight = handleHeight;
7971            invalidate();
7972        }
7973
7974        @Override
7975        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7976            setMeasuredDimension(mDrawable.getIntrinsicWidth(),
7977                    mDrawable.getIntrinsicHeight());
7978        }
7979
7980        public void show() {
7981            if (!isPositionVisible()) {
7982                hide();
7983                return;
7984            }
7985            mContainer.setContentView(this);
7986            final int[] coords = mTempCoords;
7987            TextView.this.getLocationInWindow(coords);
7988            coords[0] += mPositionX;
7989            coords[1] += mPositionY;
7990            mContainer.showAtLocation(TextView.this, 0, coords[0], coords[1]);
7991        }
7992
7993        public void hide() {
7994            mIsDragging = false;
7995            mContainer.dismiss();
7996        }
7997
7998        public boolean isShowing() {
7999            return mContainer.isShowing();
8000        }
8001
8002        private boolean isPositionVisible() {
8003            // Always show a dragging handle.
8004            if (mIsDragging) {
8005                return true;
8006            }
8007
8008            final int extendedPaddingTop = getExtendedPaddingTop();
8009            final int extendedPaddingBottom = getExtendedPaddingBottom();
8010            final int compoundPaddingLeft = getCompoundPaddingLeft();
8011            final int compoundPaddingRight = getCompoundPaddingRight();
8012
8013            final TextView hostView = TextView.this;
8014            final int left = 0;
8015            final int right = hostView.getWidth();
8016            final int top = 0;
8017            final int bottom = hostView.getHeight();
8018
8019            if (mTempRect == null) {
8020                mTempRect = new Rect();
8021            }
8022            final Rect clip = mTempRect;
8023            clip.left = left + compoundPaddingLeft;
8024            clip.top = top + extendedPaddingTop;
8025            clip.right = right - compoundPaddingRight;
8026            clip.bottom = bottom - extendedPaddingBottom;
8027
8028            final ViewParent parent = hostView.getParent();
8029            if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
8030                return false;
8031            }
8032
8033            final int[] coords = mTempCoords;
8034            hostView.getLocationInWindow(coords);
8035            final int posX = coords[0] + mPositionX + (int) mHotspotX;
8036            final int posY = coords[1] + mPositionY + (int) mHotspotY;
8037
8038            return posX >= clip.left && posX <= clip.right &&
8039                    posY >= clip.top && posY <= clip.bottom;
8040        }
8041
8042        private void moveTo(int x, int y) {
8043            mPositionX = x - TextView.this.mScrollX;
8044            mPositionY = y - TextView.this.mScrollY;
8045            if (isPositionVisible()) {
8046                int[] coords = null;
8047                if (mContainer.isShowing()){
8048                    coords = mTempCoords;
8049                    TextView.this.getLocationInWindow(coords);
8050                    mContainer.update(coords[0] + mPositionX, coords[1] + mPositionY,
8051                            mRight - mLeft, mBottom - mTop);
8052                } else {
8053                    show();
8054                }
8055
8056                if (mIsDragging) {
8057                    if (coords == null) {
8058                        coords = mTempCoords;
8059                        TextView.this.getLocationInWindow(coords);
8060                    }
8061                    if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
8062                        mTouchToWindowOffsetX += coords[0] - mLastParentX;
8063                        mTouchToWindowOffsetY += coords[1] - mLastParentY;
8064                        mLastParentX = coords[0];
8065                        mLastParentY = coords[1];
8066                    }
8067                }
8068            } else {
8069                hide();
8070            }
8071        }
8072
8073        @Override
8074        public void onDraw(Canvas c) {
8075            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
8076            if (mPositionOnTop) {
8077                c.save();
8078                c.rotate(180, (mRight - mLeft) / 2, (mBottom - mTop) / 2);
8079                mDrawable.draw(c);
8080                c.restore();
8081            } else {
8082                mDrawable.draw(c);
8083            }
8084        }
8085
8086        @Override
8087        public boolean onTouchEvent(MotionEvent ev) {
8088            switch (ev.getActionMasked()) {
8089            case MotionEvent.ACTION_DOWN: {
8090                final float rawX = ev.getRawX();
8091                final float rawY = ev.getRawY();
8092                mTouchToWindowOffsetX = rawX - mPositionX;
8093                mTouchToWindowOffsetY = rawY - mPositionY;
8094                final int[] coords = mTempCoords;
8095                TextView.this.getLocationInWindow(coords);
8096                mLastParentX = coords[0];
8097                mLastParentY = coords[1];
8098                mIsDragging = true;
8099                break;
8100            }
8101            case MotionEvent.ACTION_MOVE: {
8102                final float rawX = ev.getRawX();
8103                final float rawY = ev.getRawY();
8104                final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
8105                final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;
8106
8107                mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
8108
8109                break;
8110            }
8111            case MotionEvent.ACTION_UP:
8112            case MotionEvent.ACTION_CANCEL:
8113                mIsDragging = false;
8114            }
8115            return true;
8116        }
8117
8118        public boolean isDragging() {
8119            return mIsDragging;
8120        }
8121
8122        void positionAtCursor(final int offset, boolean bottom) {
8123            final int width = mDrawable.getIntrinsicWidth();
8124            final int height = mDrawable.getIntrinsicHeight();
8125            final int line = mLayout.getLineForOffset(offset);
8126            final int lineTop = mLayout.getLineTop(line);
8127            final int lineBottom = mLayout.getLineBottom(line);
8128
8129            final Rect bounds = sCursorControllerTempRect;
8130            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - mHotspotX)
8131                + TextView.this.mScrollX;
8132            bounds.top = (bottom ? lineBottom : lineTop - mHeight) + TextView.this.mScrollY;
8133
8134            bounds.right = bounds.left + width;
8135            bounds.bottom = bounds.top + height;
8136
8137            convertFromViewportToContentCoordinates(bounds);
8138            moveTo(bounds.left, bounds.top);
8139        }
8140    }
8141
8142    private class InsertionPointCursorController implements CursorController {
8143        private static final int DELAY_BEFORE_FADE_OUT = 4100;
8144
8145        // The cursor controller image
8146        private final HandleView mHandle;
8147
8148        private final Runnable mHider = new Runnable() {
8149            public void run() {
8150                hide();
8151            }
8152        };
8153
8154        InsertionPointCursorController() {
8155            mHandle = new HandleView(this, HandleView.CENTER);
8156        }
8157
8158        public void show() {
8159            updatePosition();
8160            mHandle.show();
8161            hideDelayed(DELAY_BEFORE_FADE_OUT);
8162        }
8163
8164        public void hide() {
8165            mHandle.hide();
8166            TextView.this.removeCallbacks(mHider);
8167        }
8168
8169        private void hideDelayed(int msec) {
8170            TextView.this.removeCallbacks(mHider);
8171            TextView.this.postDelayed(mHider, msec);
8172        }
8173
8174        public boolean isShowing() {
8175            return mHandle.isShowing();
8176        }
8177
8178        public void updatePosition(HandleView handle, int x, int y) {
8179            final int previousOffset = getSelectionStart();
8180            int offset = getHysteresisOffset(x, y, previousOffset);
8181
8182            if (offset != previousOffset) {
8183                Selection.setSelection((Spannable) mText, offset);
8184                updatePosition();
8185            }
8186            hideDelayed(DELAY_BEFORE_FADE_OUT);
8187        }
8188
8189        public void updatePosition() {
8190            final int offset = getSelectionStart();
8191
8192            if (offset < 0) {
8193                // Should never happen, safety check.
8194                Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
8195                hide();
8196                return;
8197            }
8198
8199            mHandle.positionAtCursor(offset, true);
8200        }
8201
8202        public boolean onTouchEvent(MotionEvent ev) {
8203            return false;
8204        }
8205
8206        public void onTouchModeChanged(boolean isInTouchMode) {
8207            if (!isInTouchMode) {
8208                hide();
8209            }
8210        }
8211    }
8212
8213    private class SelectionModifierCursorController implements CursorController {
8214        // The cursor controller images
8215        private HandleView mStartHandle, mEndHandle;
8216        // The offsets of that last touch down event. Remembered to start selection there.
8217        private int mMinTouchOffset, mMaxTouchOffset;
8218        // Whether selection anchors are active
8219        private boolean mIsShowing;
8220
8221        private static final int DELAY_BEFORE_FADE_OUT = 4100;
8222
8223        private final Runnable mHider = new Runnable() {
8224            public void run() {
8225                hide();
8226            }
8227        };
8228
8229        SelectionModifierCursorController() {
8230            mStartHandle = new HandleView(this, HandleView.LEFT);
8231            mEndHandle = new HandleView(this, HandleView.RIGHT);
8232            resetTouchOffsets();
8233        }
8234
8235        public void show() {
8236            mIsShowing = true;
8237            updatePosition();
8238            mStartHandle.show();
8239            mEndHandle.show();
8240            hideInsertionPointCursorController();
8241            hideDelayed(DELAY_BEFORE_FADE_OUT);
8242        }
8243
8244        public void hide() {
8245            mStartHandle.hide();
8246            mEndHandle.hide();
8247            mIsShowing = false;
8248            removeCallbacks(mHider);
8249        }
8250
8251        private void hideDelayed(int delay) {
8252            removeCallbacks(mHider);
8253            postDelayed(mHider, delay);
8254        }
8255
8256        public boolean isShowing() {
8257            return mIsShowing;
8258        }
8259
8260        public void cancelFadeOutAnimation() {
8261            hide();
8262        }
8263
8264        public void updatePosition(HandleView handle, int x, int y) {
8265            int selectionStart = getSelectionStart();
8266            int selectionEnd = getSelectionEnd();
8267
8268            final int previousOffset = handle == mStartHandle ? selectionStart : selectionEnd;
8269            int offset = getHysteresisOffset(x, y, previousOffset);
8270
8271            // Handle the case where start and end are swapped, making sure start <= end
8272            if (handle == mStartHandle) {
8273                if (selectionStart == offset || offset > selectionEnd) {
8274                    return; // no change, no need to redraw;
8275                }
8276                // If the user "closes" the selection entirely they were probably trying to
8277                // select a single character. Help them out.
8278                if (offset == selectionEnd) {
8279                    offset = selectionEnd - 1;
8280                }
8281                selectionStart = offset;
8282            } else {
8283                if (selectionEnd == offset || offset < selectionStart) {
8284                    return; // no change, no need to redraw;
8285                }
8286                // If the user "closes" the selection entirely they were probably trying to
8287                // select a single character. Help them out.
8288                if (offset == selectionStart) {
8289                    offset = selectionStart + 1;
8290                }
8291                selectionEnd = offset;
8292            }
8293
8294            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8295            updatePosition();
8296        }
8297
8298        public void updatePosition() {
8299            final int selectionStart = getSelectionStart();
8300            final int selectionEnd = getSelectionEnd();
8301
8302            if ((selectionStart < 0) || (selectionEnd < 0)) {
8303                // Should never happen, safety check.
8304                Log.w(LOG_TAG, "Update selection controller position called with no cursor");
8305                hide();
8306                return;
8307            }
8308
8309            mStartHandle.positionAtCursor(selectionStart, true);
8310            mEndHandle.positionAtCursor(selectionEnd, true);
8311            hideDelayed(DELAY_BEFORE_FADE_OUT);
8312        }
8313
8314        public boolean onTouchEvent(MotionEvent event) {
8315            // This is done even when the View does not have focus, so that long presses can start
8316            // selection and tap can move cursor from this tap position.
8317            if (isTextEditable()) {
8318                switch (event.getActionMasked()) {
8319                    case MotionEvent.ACTION_DOWN:
8320                        final int x = (int) event.getX();
8321                        final int y = (int) event.getY();
8322
8323                        // Remember finger down position, to be able to start selection from there
8324                        mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
8325
8326                        break;
8327
8328                    case MotionEvent.ACTION_POINTER_DOWN:
8329                    case MotionEvent.ACTION_POINTER_UP:
8330                        // Handle multi-point gestures. Keep min and max offset positions.
8331                        // Only activated for devices that correctly handle multi-touch.
8332                        if (mContext.getPackageManager().hasSystemFeature(
8333                                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
8334                            updateMinAndMaxOffsets(event);
8335                        }
8336                        break;
8337                }
8338            }
8339            return false;
8340        }
8341
8342        /**
8343         * @param event
8344         */
8345        private void updateMinAndMaxOffsets(MotionEvent event) {
8346            int pointerCount = event.getPointerCount();
8347            for (int index = 0; index < pointerCount; index++) {
8348                final int x = (int) event.getX(index);
8349                final int y = (int) event.getY(index);
8350                int offset = getOffset(x, y);
8351                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
8352                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
8353            }
8354        }
8355
8356        public int getMinTouchOffset() {
8357            return mMinTouchOffset;
8358        }
8359
8360        public int getMaxTouchOffset() {
8361            return mMaxTouchOffset;
8362        }
8363
8364        public void resetTouchOffsets() {
8365            mMinTouchOffset = mMaxTouchOffset = -1;
8366        }
8367
8368        /**
8369         * @return true iff this controller is currently used to move the selection start.
8370         */
8371        public boolean isSelectionStartDragged() {
8372            return mStartHandle.isDragging();
8373        }
8374
8375        public void onTouchModeChanged(boolean isInTouchMode) {
8376            if (!isInTouchMode) {
8377                hide();
8378            }
8379        }
8380    }
8381
8382    private void hideInsertionPointCursorController() {
8383        if (mInsertionPointCursorController != null) {
8384            mInsertionPointCursorController.hide();
8385        }
8386    }
8387
8388    private void hideSelectionModifierCursorController() {
8389        if (mSelectionModifierCursorController != null) {
8390            mSelectionModifierCursorController.hide();
8391        }
8392    }
8393
8394    private void hideControllers() {
8395        hideInsertionPointCursorController();
8396        hideSelectionModifierCursorController();
8397    }
8398
8399    private int getOffsetForHorizontal(int line, int x) {
8400        x -= getTotalPaddingLeft();
8401        // Clamp the position to inside of the view.
8402        x = Math.max(0, x);
8403        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8404        x += getScrollX();
8405        return getLayout().getOffsetForHorizontal(line, x);
8406    }
8407
8408    /**
8409     * Get the offset character closest to the specified absolute position.
8410     *
8411     * @param x The horizontal absolute position of a point on screen
8412     * @param y The vertical absolute position of a point on screen
8413     * @return the character offset for the character whose position is closest to the specified
8414     *  position. Returns -1 if there is no layout.
8415     *
8416     * @hide
8417     */
8418    public int getOffset(int x, int y) {
8419        if (getLayout() == null) return -1;
8420
8421        y -= getTotalPaddingTop();
8422        // Clamp the position to inside of the view.
8423        y = Math.max(0, y);
8424        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8425        y += getScrollY();
8426
8427        final int line = getLayout().getLineForVertical(y);
8428        final int offset = getOffsetForHorizontal(line, x);
8429        return offset;
8430    }
8431
8432    int getHysteresisOffset(int x, int y, int previousOffset) {
8433        final Layout layout = getLayout();
8434        if (layout == null) return -1;
8435
8436        y -= getTotalPaddingTop();
8437        // Clamp the position to inside of the view.
8438        y = Math.max(0, y);
8439        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8440        y += getScrollY();
8441
8442        int line = getLayout().getLineForVertical(y);
8443
8444        final int previousLine = layout.getLineForOffset(previousOffset);
8445        final int previousLineTop = layout.getLineTop(previousLine);
8446        final int previousLineBottom = layout.getLineBottom(previousLine);
8447        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 8;
8448
8449        // If new line is just before or after previous line and y position is less than
8450        // hysteresisThreshold away from previous line, keep cursor on previous line.
8451        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) ||
8452            ((line == previousLine - 1) && ((previousLineTop - y)    < hysteresisThreshold))) {
8453            line = previousLine;
8454        }
8455
8456        return getOffsetForHorizontal(line, x);
8457    }
8458
8459
8460    @ViewDebug.ExportedProperty(category = "text")
8461    private CharSequence            mText;
8462    private CharSequence            mTransformed;
8463    private BufferType              mBufferType = BufferType.NORMAL;
8464
8465    private int                     mInputType = EditorInfo.TYPE_NULL;
8466    private CharSequence            mHint;
8467    private Layout                  mHintLayout;
8468
8469    private KeyListener             mInput;
8470
8471    private MovementMethod          mMovement;
8472    private TransformationMethod    mTransformation;
8473    private ChangeWatcher           mChangeWatcher;
8474
8475    private ArrayList<TextWatcher>  mListeners = null;
8476
8477    // display attributes
8478    private final TextPaint         mTextPaint;
8479    private boolean                 mUserSetTextScaleX;
8480    private final Paint             mHighlightPaint;
8481    private int                     mHighlightColor = 0xCC475925;
8482    private Layout                  mLayout;
8483
8484    private long                    mShowCursor;
8485    private Blink                   mBlink;
8486    private boolean                 mCursorVisible = true;
8487
8488    // Cursor Controllers. Null when disabled.
8489    private CursorController        mInsertionPointCursorController;
8490    private CursorController        mSelectionModifierCursorController;
8491    private ActionMode              mSelectionActionMode;
8492    // These are needed to desambiguate a long click. If the long click comes from ones of these, we
8493    // select from the current cursor position. Otherwise, select from long pressed position.
8494    private boolean                 mDPadCenterIsDown = false;
8495    private boolean                 mEnterKeyIsDown = false;
8496    private boolean                 mContextMenuTriggeredByKey = false;
8497    // Created once and shared by different CursorController helper methods.
8498    // Only one cursor controller is active at any time which prevent race conditions.
8499    private static Rect             sCursorControllerTempRect = new Rect();
8500
8501    private boolean                 mSelectAllOnFocus = false;
8502
8503    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
8504    private boolean                 mHorizontallyScrolling;
8505
8506    private int                     mAutoLinkMask;
8507    private boolean                 mLinksClickable = true;
8508
8509    private float                   mSpacingMult = 1;
8510    private float                   mSpacingAdd = 0;
8511    private int                     mLineHeight = 0;
8512
8513    private static final int        LINES = 1;
8514    private static final int        EMS = LINES;
8515    private static final int        PIXELS = 2;
8516
8517    private int                     mMaximum = Integer.MAX_VALUE;
8518    private int                     mMaxMode = LINES;
8519    private int                     mMinimum = 0;
8520    private int                     mMinMode = LINES;
8521
8522    private int                     mMaxWidth = Integer.MAX_VALUE;
8523    private int                     mMaxWidthMode = PIXELS;
8524    private int                     mMinWidth = 0;
8525    private int                     mMinWidthMode = PIXELS;
8526
8527    private boolean                 mSingleLine;
8528    private int                     mDesiredHeightAtMeasure = -1;
8529    private boolean                 mIncludePad = true;
8530
8531    // tmp primitives, so we don't alloc them on each draw
8532    private Path                    mHighlightPath;
8533    private boolean                 mHighlightPathBogus = true;
8534    private static final RectF      sTempRect = new RectF();
8535
8536    // XXX should be much larger
8537    private static final int        VERY_WIDE = 16384;
8538
8539    private static final int        BLINK = 500;
8540
8541    private static final int ANIMATED_SCROLL_GAP = 250;
8542    private long mLastScroll;
8543    private Scroller mScroller = null;
8544
8545    private BoringLayout.Metrics mBoring;
8546    private BoringLayout.Metrics mHintBoring;
8547
8548    private BoringLayout mSavedLayout, mSavedHintLayout;
8549
8550    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
8551    private InputFilter[] mFilters = NO_FILTERS;
8552    private static final Spanned EMPTY_SPANNED = new SpannedString("");
8553}
8554