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