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