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