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