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