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