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