TextView.java revision 5b2081dc41cccd76780a2cb4e9a973505c13446c
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        notifyViewAccessibilityStateChangedIfNeeded();
4410    }
4411
4412    @Override
4413    protected boolean setFrame(int l, int t, int r, int b) {
4414        boolean result = super.setFrame(l, t, r, b);
4415
4416        if (mEditor != null) mEditor.setFrame();
4417
4418        restartMarqueeIfNeeded();
4419
4420        return result;
4421    }
4422
4423    private void restartMarqueeIfNeeded() {
4424        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4425            mRestartMarquee = false;
4426            startMarquee();
4427        }
4428    }
4429
4430    /**
4431     * Sets the list of input filters that will be used if the buffer is
4432     * Editable. Has no effect otherwise.
4433     *
4434     * @attr ref android.R.styleable#TextView_maxLength
4435     */
4436    public void setFilters(InputFilter[] filters) {
4437        if (filters == null) {
4438            throw new IllegalArgumentException();
4439        }
4440
4441        mFilters = filters;
4442
4443        if (mText instanceof Editable) {
4444            setFilters((Editable) mText, filters);
4445        }
4446    }
4447
4448    /**
4449     * Sets the list of input filters on the specified Editable,
4450     * and includes mInput in the list if it is an InputFilter.
4451     */
4452    private void setFilters(Editable e, InputFilter[] filters) {
4453        if (mEditor != null) {
4454            final boolean undoFilter = mEditor.mUndoInputFilter != null;
4455            final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4456            int num = 0;
4457            if (undoFilter) num++;
4458            if (keyFilter) num++;
4459            if (num > 0) {
4460                InputFilter[] nf = new InputFilter[filters.length + num];
4461
4462                System.arraycopy(filters, 0, nf, 0, filters.length);
4463                num = 0;
4464                if (undoFilter) {
4465                    nf[filters.length] = mEditor.mUndoInputFilter;
4466                    num++;
4467                }
4468                if (keyFilter) {
4469                    nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4470                }
4471
4472                e.setFilters(nf);
4473                return;
4474            }
4475        }
4476        e.setFilters(filters);
4477    }
4478
4479    /**
4480     * Returns the current list of input filters.
4481     *
4482     * @attr ref android.R.styleable#TextView_maxLength
4483     */
4484    public InputFilter[] getFilters() {
4485        return mFilters;
4486    }
4487
4488    /////////////////////////////////////////////////////////////////////////
4489
4490    private int getBoxHeight(Layout l) {
4491        Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4492        int padding = (l == mHintLayout) ?
4493                getCompoundPaddingTop() + getCompoundPaddingBottom() :
4494                getExtendedPaddingTop() + getExtendedPaddingBottom();
4495        return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4496    }
4497
4498    int getVerticalOffset(boolean forceNormal) {
4499        int voffset = 0;
4500        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4501
4502        Layout l = mLayout;
4503        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4504            l = mHintLayout;
4505        }
4506
4507        if (gravity != Gravity.TOP) {
4508            int boxht = getBoxHeight(l);
4509            int textht = l.getHeight();
4510
4511            if (textht < boxht) {
4512                if (gravity == Gravity.BOTTOM)
4513                    voffset = boxht - textht;
4514                else // (gravity == Gravity.CENTER_VERTICAL)
4515                    voffset = (boxht - textht) >> 1;
4516            }
4517        }
4518        return voffset;
4519    }
4520
4521    private int getBottomVerticalOffset(boolean forceNormal) {
4522        int voffset = 0;
4523        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4524
4525        Layout l = mLayout;
4526        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4527            l = mHintLayout;
4528        }
4529
4530        if (gravity != Gravity.BOTTOM) {
4531            int boxht = getBoxHeight(l);
4532            int textht = l.getHeight();
4533
4534            if (textht < boxht) {
4535                if (gravity == Gravity.TOP)
4536                    voffset = boxht - textht;
4537                else // (gravity == Gravity.CENTER_VERTICAL)
4538                    voffset = (boxht - textht) >> 1;
4539            }
4540        }
4541        return voffset;
4542    }
4543
4544    void invalidateCursorPath() {
4545        if (mHighlightPathBogus) {
4546            invalidateCursor();
4547        } else {
4548            final int horizontalPadding = getCompoundPaddingLeft();
4549            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4550
4551            if (mEditor.mCursorCount == 0) {
4552                synchronized (TEMP_RECTF) {
4553                    /*
4554                     * The reason for this concern about the thickness of the
4555                     * cursor and doing the floor/ceil on the coordinates is that
4556                     * some EditTexts (notably textfields in the Browser) have
4557                     * anti-aliased text where not all the characters are
4558                     * necessarily at integer-multiple locations.  This should
4559                     * make sure the entire cursor gets invalidated instead of
4560                     * sometimes missing half a pixel.
4561                     */
4562                    float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4563                    if (thick < 1.0f) {
4564                        thick = 1.0f;
4565                    }
4566
4567                    thick /= 2.0f;
4568
4569                    // mHighlightPath is guaranteed to be non null at that point.
4570                    mHighlightPath.computeBounds(TEMP_RECTF, false);
4571
4572                    invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4573                            (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4574                            (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4575                            (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
4576                }
4577            } else {
4578                for (int i = 0; i < mEditor.mCursorCount; i++) {
4579                    Rect bounds = mEditor.mCursorDrawable[i].getBounds();
4580                    invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4581                            bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4582                }
4583            }
4584        }
4585    }
4586
4587    void invalidateCursor() {
4588        int where = getSelectionEnd();
4589
4590        invalidateCursor(where, where, where);
4591    }
4592
4593    private void invalidateCursor(int a, int b, int c) {
4594        if (a >= 0 || b >= 0 || c >= 0) {
4595            int start = Math.min(Math.min(a, b), c);
4596            int end = Math.max(Math.max(a, b), c);
4597            invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
4598        }
4599    }
4600
4601    /**
4602     * Invalidates the region of text enclosed between the start and end text offsets.
4603     */
4604    void invalidateRegion(int start, int end, boolean invalidateCursor) {
4605        if (mLayout == null) {
4606            invalidate();
4607        } else {
4608                int lineStart = mLayout.getLineForOffset(start);
4609                int top = mLayout.getLineTop(lineStart);
4610
4611                // This is ridiculous, but the descent from the line above
4612                // can hang down into the line we really want to redraw,
4613                // so we have to invalidate part of the line above to make
4614                // sure everything that needs to be redrawn really is.
4615                // (But not the whole line above, because that would cause
4616                // the same problem with the descenders on the line above it!)
4617                if (lineStart > 0) {
4618                    top -= mLayout.getLineDescent(lineStart - 1);
4619                }
4620
4621                int lineEnd;
4622
4623                if (start == end)
4624                    lineEnd = lineStart;
4625                else
4626                    lineEnd = mLayout.getLineForOffset(end);
4627
4628                int bottom = mLayout.getLineBottom(lineEnd);
4629
4630                // mEditor can be null in case selection is set programmatically.
4631                if (invalidateCursor && mEditor != null) {
4632                    for (int i = 0; i < mEditor.mCursorCount; i++) {
4633                        Rect bounds = mEditor.mCursorDrawable[i].getBounds();
4634                        top = Math.min(top, bounds.top);
4635                        bottom = Math.max(bottom, bounds.bottom);
4636                    }
4637                }
4638
4639                final int compoundPaddingLeft = getCompoundPaddingLeft();
4640                final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4641
4642                int left, right;
4643                if (lineStart == lineEnd && !invalidateCursor) {
4644                    left = (int) mLayout.getPrimaryHorizontal(start);
4645                    right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4646                    left += compoundPaddingLeft;
4647                    right += compoundPaddingLeft;
4648                } else {
4649                    // Rectangle bounding box when the region spans several lines
4650                    left = compoundPaddingLeft;
4651                    right = getWidth() - getCompoundPaddingRight();
4652                }
4653
4654                invalidate(mScrollX + left, verticalPadding + top,
4655                        mScrollX + right, verticalPadding + bottom);
4656        }
4657    }
4658
4659    private void registerForPreDraw() {
4660        if (!mPreDrawRegistered) {
4661            getViewTreeObserver().addOnPreDrawListener(this);
4662            mPreDrawRegistered = true;
4663        }
4664    }
4665
4666    /**
4667     * {@inheritDoc}
4668     */
4669    public boolean onPreDraw() {
4670        if (mLayout == null) {
4671            assumeLayout();
4672        }
4673
4674        boolean changed = false;
4675
4676        if (mMovement != null) {
4677            /* This code also provides auto-scrolling when a cursor is moved using a
4678             * CursorController (insertion point or selection limits).
4679             * For selection, ensure start or end is visible depending on controller's state.
4680             */
4681            int curs = getSelectionEnd();
4682            // Do not create the controller if it is not already created.
4683            if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4684                    mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
4685                curs = getSelectionStart();
4686            }
4687
4688            /*
4689             * TODO: This should really only keep the end in view if
4690             * it already was before the text changed.  I'm not sure
4691             * of a good way to tell from here if it was.
4692             */
4693            if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
4694                curs = mText.length();
4695            }
4696
4697            if (curs >= 0) {
4698                changed = bringPointIntoView(curs);
4699            }
4700        } else {
4701            changed = bringTextIntoView();
4702        }
4703
4704        // This has to be checked here since:
4705        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4706        //   a screen rotation) since layout is not yet initialized at that point.
4707        if (mEditor != null && mEditor.mCreatedWithASelection) {
4708            mEditor.startSelectionActionMode();
4709            mEditor.mCreatedWithASelection = false;
4710        }
4711
4712        // Phone specific code (there is no ExtractEditText on tablets).
4713        // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4714        // not be set. Do the test here instead.
4715        if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
4716            mEditor.startSelectionActionMode();
4717        }
4718
4719        getViewTreeObserver().removeOnPreDrawListener(this);
4720        mPreDrawRegistered = false;
4721
4722        return !changed;
4723    }
4724
4725    @Override
4726    protected void onAttachedToWindow() {
4727        super.onAttachedToWindow();
4728
4729        mTemporaryDetach = false;
4730
4731        if (mEditor != null) mEditor.onAttachedToWindow();
4732    }
4733
4734    @Override
4735    protected void onDetachedFromWindow() {
4736        super.onDetachedFromWindow();
4737
4738        if (mPreDrawRegistered) {
4739            getViewTreeObserver().removeOnPreDrawListener(this);
4740            mPreDrawRegistered = false;
4741        }
4742
4743        resetResolvedDrawables();
4744
4745        if (mEditor != null) mEditor.onDetachedFromWindow();
4746    }
4747
4748    @Override
4749    public void onScreenStateChanged(int screenState) {
4750        super.onScreenStateChanged(screenState);
4751        if (mEditor != null) mEditor.onScreenStateChanged(screenState);
4752    }
4753
4754    @Override
4755    protected boolean isPaddingOffsetRequired() {
4756        return mShadowRadius != 0 || mDrawables != null;
4757    }
4758
4759    @Override
4760    protected int getLeftPaddingOffset() {
4761        return getCompoundPaddingLeft() - mPaddingLeft +
4762                (int) Math.min(0, mShadowDx - mShadowRadius);
4763    }
4764
4765    @Override
4766    protected int getTopPaddingOffset() {
4767        return (int) Math.min(0, mShadowDy - mShadowRadius);
4768    }
4769
4770    @Override
4771    protected int getBottomPaddingOffset() {
4772        return (int) Math.max(0, mShadowDy + mShadowRadius);
4773    }
4774
4775    @Override
4776    protected int getRightPaddingOffset() {
4777        return -(getCompoundPaddingRight() - mPaddingRight) +
4778                (int) Math.max(0, mShadowDx + mShadowRadius);
4779    }
4780
4781    @Override
4782    protected boolean verifyDrawable(Drawable who) {
4783        final boolean verified = super.verifyDrawable(who);
4784        if (!verified && mDrawables != null) {
4785            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
4786                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4787                    who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
4788        }
4789        return verified;
4790    }
4791
4792    @Override
4793    public void jumpDrawablesToCurrentState() {
4794        super.jumpDrawablesToCurrentState();
4795        if (mDrawables != null) {
4796            if (mDrawables.mDrawableLeft != null) {
4797                mDrawables.mDrawableLeft.jumpToCurrentState();
4798            }
4799            if (mDrawables.mDrawableTop != null) {
4800                mDrawables.mDrawableTop.jumpToCurrentState();
4801            }
4802            if (mDrawables.mDrawableRight != null) {
4803                mDrawables.mDrawableRight.jumpToCurrentState();
4804            }
4805            if (mDrawables.mDrawableBottom != null) {
4806                mDrawables.mDrawableBottom.jumpToCurrentState();
4807            }
4808            if (mDrawables.mDrawableStart != null) {
4809                mDrawables.mDrawableStart.jumpToCurrentState();
4810            }
4811            if (mDrawables.mDrawableEnd != null) {
4812                mDrawables.mDrawableEnd.jumpToCurrentState();
4813            }
4814        }
4815    }
4816
4817    @Override
4818    public void invalidateDrawable(Drawable drawable) {
4819        if (verifyDrawable(drawable)) {
4820            final Rect dirty = drawable.getBounds();
4821            int scrollX = mScrollX;
4822            int scrollY = mScrollY;
4823
4824            // IMPORTANT: The coordinates below are based on the coordinates computed
4825            // for each compound drawable in onDraw(). Make sure to update each section
4826            // accordingly.
4827            final TextView.Drawables drawables = mDrawables;
4828            if (drawables != null) {
4829                if (drawable == drawables.mDrawableLeft) {
4830                    final int compoundPaddingTop = getCompoundPaddingTop();
4831                    final int compoundPaddingBottom = getCompoundPaddingBottom();
4832                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4833
4834                    scrollX += mPaddingLeft;
4835                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4836                } else if (drawable == drawables.mDrawableRight) {
4837                    final int compoundPaddingTop = getCompoundPaddingTop();
4838                    final int compoundPaddingBottom = getCompoundPaddingBottom();
4839                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4840
4841                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4842                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4843                } else if (drawable == drawables.mDrawableTop) {
4844                    final int compoundPaddingLeft = getCompoundPaddingLeft();
4845                    final int compoundPaddingRight = getCompoundPaddingRight();
4846                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
4847
4848                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4849                    scrollY += mPaddingTop;
4850                } else if (drawable == drawables.mDrawableBottom) {
4851                    final int compoundPaddingLeft = getCompoundPaddingLeft();
4852                    final int compoundPaddingRight = getCompoundPaddingRight();
4853                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
4854
4855                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4856                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4857                }
4858            }
4859
4860            invalidate(dirty.left + scrollX, dirty.top + scrollY,
4861                    dirty.right + scrollX, dirty.bottom + scrollY);
4862        }
4863    }
4864
4865    @Override
4866    public boolean hasOverlappingRendering() {
4867        return ((getBackground() != null && getBackground().getCurrent() != null)
4868                || mText instanceof Spannable || hasSelection());
4869    }
4870
4871    /**
4872     *
4873     * Returns the state of the {@code textIsSelectable} flag (See
4874     * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
4875     * to allow users to select and copy text in a non-editable TextView, the content of an
4876     * {@link EditText} can always be selected, independently of the value of this flag.
4877     * <p>
4878     *
4879     * @return True if the text displayed in this TextView can be selected by the user.
4880     *
4881     * @attr ref android.R.styleable#TextView_textIsSelectable
4882     */
4883    public boolean isTextSelectable() {
4884        return mEditor == null ? false : mEditor.mTextIsSelectable;
4885    }
4886
4887    /**
4888     * Sets whether the content of this view is selectable by the user. The default is
4889     * {@code false}, meaning that the content is not selectable.
4890     * <p>
4891     * When you use a TextView to display a useful piece of information to the user (such as a
4892     * contact's address), make it selectable, so that the user can select and copy its
4893     * content. You can also use set the XML attribute
4894     * {@link android.R.styleable#TextView_textIsSelectable} to "true".
4895     * <p>
4896     * When you call this method to set the value of {@code textIsSelectable}, it sets
4897     * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
4898     * and {@code longClickable} to the same value. These flags correspond to the attributes
4899     * {@link android.R.styleable#View_focusable android:focusable},
4900     * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
4901     * {@link android.R.styleable#View_clickable android:clickable}, and
4902     * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
4903     * flags to a state you had set previously, call one or more of the following methods:
4904     * {@link #setFocusable(boolean) setFocusable()},
4905     * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
4906     * {@link #setClickable(boolean) setClickable()} or
4907     * {@link #setLongClickable(boolean) setLongClickable()}.
4908     *
4909     * @param selectable Whether the content of this TextView should be selectable.
4910     */
4911    public void setTextIsSelectable(boolean selectable) {
4912        if (!selectable && mEditor == null) return; // false is default value with no edit data
4913
4914        createEditorIfNeeded();
4915        if (mEditor.mTextIsSelectable == selectable) return;
4916
4917        mEditor.mTextIsSelectable = selectable;
4918        setFocusableInTouchMode(selectable);
4919        setFocusable(selectable);
4920        setClickable(selectable);
4921        setLongClickable(selectable);
4922
4923        // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
4924
4925        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
4926        setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
4927
4928        // Called by setText above, but safer in case of future code changes
4929        mEditor.prepareCursorControllers();
4930    }
4931
4932    @Override
4933    protected int[] onCreateDrawableState(int extraSpace) {
4934        final int[] drawableState;
4935
4936        if (mSingleLine) {
4937            drawableState = super.onCreateDrawableState(extraSpace);
4938        } else {
4939            drawableState = super.onCreateDrawableState(extraSpace + 1);
4940            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4941        }
4942
4943        if (isTextSelectable()) {
4944            // Disable pressed state, which was introduced when TextView was made clickable.
4945            // Prevents text color change.
4946            // setClickable(false) would have a similar effect, but it also disables focus changes
4947            // and long press actions, which are both needed by text selection.
4948            final int length = drawableState.length;
4949            for (int i = 0; i < length; i++) {
4950                if (drawableState[i] == R.attr.state_pressed) {
4951                    final int[] nonPressedState = new int[length - 1];
4952                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4953                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4954                    return nonPressedState;
4955                }
4956            }
4957        }
4958
4959        return drawableState;
4960    }
4961
4962    private Path getUpdatedHighlightPath() {
4963        Path highlight = null;
4964        Paint highlightPaint = mHighlightPaint;
4965
4966        final int selStart = getSelectionStart();
4967        final int selEnd = getSelectionEnd();
4968        if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4969            if (selStart == selEnd) {
4970                if (mEditor != null && mEditor.isCursorVisible() &&
4971                        (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
4972                        (2 * Editor.BLINK) < Editor.BLINK) {
4973                    if (mHighlightPathBogus) {
4974                        if (mHighlightPath == null) mHighlightPath = new Path();
4975                        mHighlightPath.reset();
4976                        mLayout.getCursorPath(selStart, mHighlightPath, mText);
4977                        mEditor.updateCursorsPositions();
4978                        mHighlightPathBogus = false;
4979                    }
4980
4981                    // XXX should pass to skin instead of drawing directly
4982                    highlightPaint.setColor(mCurTextColor);
4983                    highlightPaint.setStyle(Paint.Style.STROKE);
4984                    highlight = mHighlightPath;
4985                }
4986            } else {
4987                if (mHighlightPathBogus) {
4988                    if (mHighlightPath == null) mHighlightPath = new Path();
4989                    mHighlightPath.reset();
4990                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4991                    mHighlightPathBogus = false;
4992                }
4993
4994                // XXX should pass to skin instead of drawing directly
4995                highlightPaint.setColor(mHighlightColor);
4996                highlightPaint.setStyle(Paint.Style.FILL);
4997
4998                highlight = mHighlightPath;
4999            }
5000        }
5001        return highlight;
5002    }
5003
5004    /**
5005     * @hide
5006     */
5007    public int getHorizontalOffsetForDrawables() {
5008        return 0;
5009    }
5010
5011    @Override
5012    protected void onDraw(Canvas canvas) {
5013        restartMarqueeIfNeeded();
5014
5015        // Draw the background for this view
5016        super.onDraw(canvas);
5017
5018        final int compoundPaddingLeft = getCompoundPaddingLeft();
5019        final int compoundPaddingTop = getCompoundPaddingTop();
5020        final int compoundPaddingRight = getCompoundPaddingRight();
5021        final int compoundPaddingBottom = getCompoundPaddingBottom();
5022        final int scrollX = mScrollX;
5023        final int scrollY = mScrollY;
5024        final int right = mRight;
5025        final int left = mLeft;
5026        final int bottom = mBottom;
5027        final int top = mTop;
5028        final boolean isLayoutRtl = isLayoutRtl();
5029        final int offset = getHorizontalOffsetForDrawables();
5030        final int leftOffset = isLayoutRtl ? 0 : offset;
5031        final int rightOffset = isLayoutRtl ? offset : 0 ;
5032
5033        final Drawables dr = mDrawables;
5034        if (dr != null) {
5035            /*
5036             * Compound, not extended, because the icon is not clipped
5037             * if the text height is smaller.
5038             */
5039
5040            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5041            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5042
5043            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5044            // Make sure to update invalidateDrawable() when changing this code.
5045            if (dr.mDrawableLeft != null) {
5046                canvas.save();
5047                canvas.translate(scrollX + mPaddingLeft + leftOffset,
5048                                 scrollY + compoundPaddingTop +
5049                                 (vspace - dr.mDrawableHeightLeft) / 2);
5050                dr.mDrawableLeft.draw(canvas);
5051                canvas.restore();
5052            }
5053
5054            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5055            // Make sure to update invalidateDrawable() when changing this code.
5056            if (dr.mDrawableRight != null) {
5057                canvas.save();
5058                canvas.translate(scrollX + right - left - mPaddingRight
5059                        - dr.mDrawableSizeRight - rightOffset,
5060                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5061                dr.mDrawableRight.draw(canvas);
5062                canvas.restore();
5063            }
5064
5065            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5066            // Make sure to update invalidateDrawable() when changing this code.
5067            if (dr.mDrawableTop != null) {
5068                canvas.save();
5069                canvas.translate(scrollX + compoundPaddingLeft +
5070                        (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5071                dr.mDrawableTop.draw(canvas);
5072                canvas.restore();
5073            }
5074
5075            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5076            // Make sure to update invalidateDrawable() when changing this code.
5077            if (dr.mDrawableBottom != null) {
5078                canvas.save();
5079                canvas.translate(scrollX + compoundPaddingLeft +
5080                        (hspace - dr.mDrawableWidthBottom) / 2,
5081                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5082                dr.mDrawableBottom.draw(canvas);
5083                canvas.restore();
5084            }
5085        }
5086
5087        int color = mCurTextColor;
5088
5089        if (mLayout == null) {
5090            assumeLayout();
5091        }
5092
5093        Layout layout = mLayout;
5094
5095        if (mHint != null && mText.length() == 0) {
5096            if (mHintTextColor != null) {
5097                color = mCurHintTextColor;
5098            }
5099
5100            layout = mHintLayout;
5101        }
5102
5103        mTextPaint.setColor(color);
5104        mTextPaint.drawableState = getDrawableState();
5105
5106        canvas.save();
5107        /*  Would be faster if we didn't have to do this. Can we chop the
5108            (displayable) text so that we don't need to do this ever?
5109        */
5110
5111        int extendedPaddingTop = getExtendedPaddingTop();
5112        int extendedPaddingBottom = getExtendedPaddingBottom();
5113
5114        final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5115        final int maxScrollY = mLayout.getHeight() - vspace;
5116
5117        float clipLeft = compoundPaddingLeft + scrollX;
5118        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5119        float clipRight = right - left - compoundPaddingRight + scrollX;
5120        float clipBottom = bottom - top + scrollY -
5121                ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5122
5123        if (mShadowRadius != 0) {
5124            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5125            clipRight += Math.max(0, mShadowDx + mShadowRadius);
5126
5127            clipTop += Math.min(0, mShadowDy - mShadowRadius);
5128            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5129        }
5130
5131        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5132
5133        int voffsetText = 0;
5134        int voffsetCursor = 0;
5135
5136        // translate in by our padding
5137        /* shortcircuit calling getVerticaOffset() */
5138        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5139            voffsetText = getVerticalOffset(false);
5140            voffsetCursor = getVerticalOffset(true);
5141        }
5142        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5143
5144        final int layoutDirection = getLayoutDirection();
5145        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5146        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5147                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5148            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5149                    (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5150                final int width = mRight - mLeft;
5151                final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5152                final float dx = mLayout.getLineRight(0) - (width - padding);
5153                canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
5154            }
5155
5156            if (mMarquee != null && mMarquee.isRunning()) {
5157                final float dx = -mMarquee.getScroll();
5158                canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
5159            }
5160        }
5161
5162        final int cursorOffsetVertical = voffsetCursor - voffsetText;
5163
5164        Path highlight = getUpdatedHighlightPath();
5165        if (mEditor != null) {
5166            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5167        } else {
5168            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5169        }
5170
5171        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5172            final int dx = (int) mMarquee.getGhostOffset();
5173            canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
5174            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5175        }
5176
5177        canvas.restore();
5178    }
5179
5180    @Override
5181    public void getFocusedRect(Rect r) {
5182        if (mLayout == null) {
5183            super.getFocusedRect(r);
5184            return;
5185        }
5186
5187        int selEnd = getSelectionEnd();
5188        if (selEnd < 0) {
5189            super.getFocusedRect(r);
5190            return;
5191        }
5192
5193        int selStart = getSelectionStart();
5194        if (selStart < 0 || selStart >= selEnd) {
5195            int line = mLayout.getLineForOffset(selEnd);
5196            r.top = mLayout.getLineTop(line);
5197            r.bottom = mLayout.getLineBottom(line);
5198            r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5199            r.right = r.left + 4;
5200        } else {
5201            int lineStart = mLayout.getLineForOffset(selStart);
5202            int lineEnd = mLayout.getLineForOffset(selEnd);
5203            r.top = mLayout.getLineTop(lineStart);
5204            r.bottom = mLayout.getLineBottom(lineEnd);
5205            if (lineStart == lineEnd) {
5206                r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5207                r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5208            } else {
5209                // Selection extends across multiple lines -- make the focused
5210                // rect cover the entire width.
5211                if (mHighlightPathBogus) {
5212                    if (mHighlightPath == null) mHighlightPath = new Path();
5213                    mHighlightPath.reset();
5214                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5215                    mHighlightPathBogus = false;
5216                }
5217                synchronized (TEMP_RECTF) {
5218                    mHighlightPath.computeBounds(TEMP_RECTF, true);
5219                    r.left = (int)TEMP_RECTF.left-1;
5220                    r.right = (int)TEMP_RECTF.right+1;
5221                }
5222            }
5223        }
5224
5225        // Adjust for padding and gravity.
5226        int paddingLeft = getCompoundPaddingLeft();
5227        int paddingTop = getExtendedPaddingTop();
5228        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5229            paddingTop += getVerticalOffset(false);
5230        }
5231        r.offset(paddingLeft, paddingTop);
5232        int paddingBottom = getExtendedPaddingBottom();
5233        r.bottom += paddingBottom;
5234    }
5235
5236    /**
5237     * Return the number of lines of text, or 0 if the internal Layout has not
5238     * been built.
5239     */
5240    public int getLineCount() {
5241        return mLayout != null ? mLayout.getLineCount() : 0;
5242    }
5243
5244    /**
5245     * Return the baseline for the specified line (0...getLineCount() - 1)
5246     * If bounds is not null, return the top, left, right, bottom extents
5247     * of the specified line in it. If the internal Layout has not been built,
5248     * return 0 and set bounds to (0, 0, 0, 0)
5249     * @param line which line to examine (0..getLineCount() - 1)
5250     * @param bounds Optional. If not null, it returns the extent of the line
5251     * @return the Y-coordinate of the baseline
5252     */
5253    public int getLineBounds(int line, Rect bounds) {
5254        if (mLayout == null) {
5255            if (bounds != null) {
5256                bounds.set(0, 0, 0, 0);
5257            }
5258            return 0;
5259        }
5260        else {
5261            int baseline = mLayout.getLineBounds(line, bounds);
5262
5263            int voffset = getExtendedPaddingTop();
5264            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5265                voffset += getVerticalOffset(true);
5266            }
5267            if (bounds != null) {
5268                bounds.offset(getCompoundPaddingLeft(), voffset);
5269            }
5270            return baseline + voffset;
5271        }
5272    }
5273
5274    @Override
5275    public int getBaseline() {
5276        if (mLayout == null) {
5277            return super.getBaseline();
5278        }
5279
5280        int voffset = 0;
5281        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5282            voffset = getVerticalOffset(true);
5283        }
5284
5285        if (isLayoutModeOptical(mParent)) {
5286            voffset -= getOpticalInsets().top;
5287        }
5288
5289        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5290    }
5291
5292    /**
5293     * @hide
5294     */
5295    @Override
5296    protected int getFadeTop(boolean offsetRequired) {
5297        if (mLayout == null) return 0;
5298
5299        int voffset = 0;
5300        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5301            voffset = getVerticalOffset(true);
5302        }
5303
5304        if (offsetRequired) voffset += getTopPaddingOffset();
5305
5306        return getExtendedPaddingTop() + voffset;
5307    }
5308
5309    /**
5310     * @hide
5311     */
5312    @Override
5313    protected int getFadeHeight(boolean offsetRequired) {
5314        return mLayout != null ? mLayout.getHeight() : 0;
5315    }
5316
5317    @Override
5318    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5319        if (keyCode == KeyEvent.KEYCODE_BACK) {
5320            boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
5321
5322            if (isInSelectionMode) {
5323                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5324                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5325                    if (state != null) {
5326                        state.startTracking(event, this);
5327                    }
5328                    return true;
5329                } else if (event.getAction() == KeyEvent.ACTION_UP) {
5330                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5331                    if (state != null) {
5332                        state.handleUpEvent(event);
5333                    }
5334                    if (event.isTracking() && !event.isCanceled()) {
5335                        stopSelectionActionMode();
5336                        return true;
5337                    }
5338                }
5339            }
5340        }
5341        return super.onKeyPreIme(keyCode, event);
5342    }
5343
5344    @Override
5345    public boolean onKeyDown(int keyCode, KeyEvent event) {
5346        int which = doKeyDown(keyCode, event, null);
5347        if (which == 0) {
5348            return super.onKeyDown(keyCode, event);
5349        }
5350
5351        return true;
5352    }
5353
5354    @Override
5355    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5356        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5357
5358        int which = doKeyDown(keyCode, down, event);
5359        if (which == 0) {
5360            // Go through default dispatching.
5361            return super.onKeyMultiple(keyCode, repeatCount, event);
5362        }
5363        if (which == -1) {
5364            // Consumed the whole thing.
5365            return true;
5366        }
5367
5368        repeatCount--;
5369
5370        // We are going to dispatch the remaining events to either the input
5371        // or movement method.  To do this, we will just send a repeated stream
5372        // of down and up events until we have done the complete repeatCount.
5373        // It would be nice if those interfaces had an onKeyMultiple() method,
5374        // but adding that is a more complicated change.
5375        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5376        if (which == 1) {
5377            // mEditor and mEditor.mInput are not null from doKeyDown
5378            mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5379            while (--repeatCount > 0) {
5380                mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5381                mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5382            }
5383            hideErrorIfUnchanged();
5384
5385        } else if (which == 2) {
5386            // mMovement is not null from doKeyDown
5387            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5388            while (--repeatCount > 0) {
5389                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5390                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5391            }
5392        }
5393
5394        return true;
5395    }
5396
5397    /**
5398     * Returns true if pressing ENTER in this field advances focus instead
5399     * of inserting the character.  This is true mostly in single-line fields,
5400     * but also in mail addresses and subjects which will display on multiple
5401     * lines but where it doesn't make sense to insert newlines.
5402     */
5403    private boolean shouldAdvanceFocusOnEnter() {
5404        if (getKeyListener() == null) {
5405            return false;
5406        }
5407
5408        if (mSingleLine) {
5409            return true;
5410        }
5411
5412        if (mEditor != null &&
5413                (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5414            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5415            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5416                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5417                return true;
5418            }
5419        }
5420
5421        return false;
5422    }
5423
5424    /**
5425     * Returns true if pressing TAB in this field advances focus instead
5426     * of inserting the character.  Insert tabs only in multi-line editors.
5427     */
5428    private boolean shouldAdvanceFocusOnTab() {
5429        if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5430                (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5431            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5432            if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5433                    || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5434                return false;
5435            }
5436        }
5437        return true;
5438    }
5439
5440    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5441        if (!isEnabled()) {
5442            return 0;
5443        }
5444
5445        // If this is the initial keydown, we don't want to prevent a movement away from this view.
5446        // While this shouldn't be necessary because any time we're preventing default movement we
5447        // should be restricting the focus to remain within this view, thus we'll also receive
5448        // the key up event, occasionally key up events will get dropped and we don't want to
5449        // prevent the user from traversing out of this on the next key down.
5450        if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5451            mPreventDefaultMovement = false;
5452        }
5453
5454        switch (keyCode) {
5455            case KeyEvent.KEYCODE_ENTER:
5456                if (event.hasNoModifiers()) {
5457                    // When mInputContentType is set, we know that we are
5458                    // running in a "modern" cupcake environment, so don't need
5459                    // to worry about the application trying to capture
5460                    // enter key events.
5461                    if (mEditor != null && mEditor.mInputContentType != null) {
5462                        // If there is an action listener, given them a
5463                        // chance to consume the event.
5464                        if (mEditor.mInputContentType.onEditorActionListener != null &&
5465                                mEditor.mInputContentType.onEditorActionListener.onEditorAction(
5466                                this, EditorInfo.IME_NULL, event)) {
5467                            mEditor.mInputContentType.enterDown = true;
5468                            // We are consuming the enter key for them.
5469                            return -1;
5470                        }
5471                    }
5472
5473                    // If our editor should move focus when enter is pressed, or
5474                    // this is a generated event from an IME action button, then
5475                    // don't let it be inserted into the text.
5476                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5477                            || shouldAdvanceFocusOnEnter()) {
5478                        if (hasOnClickListeners()) {
5479                            return 0;
5480                        }
5481                        return -1;
5482                    }
5483                }
5484                break;
5485
5486            case KeyEvent.KEYCODE_DPAD_CENTER:
5487                if (event.hasNoModifiers()) {
5488                    if (shouldAdvanceFocusOnEnter()) {
5489                        return 0;
5490                    }
5491                }
5492                break;
5493
5494            case KeyEvent.KEYCODE_TAB:
5495                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5496                    if (shouldAdvanceFocusOnTab()) {
5497                        return 0;
5498                    }
5499                }
5500                break;
5501
5502                // Has to be done on key down (and not on key up) to correctly be intercepted.
5503            case KeyEvent.KEYCODE_BACK:
5504                if (mEditor != null && mEditor.mSelectionActionMode != null) {
5505                    stopSelectionActionMode();
5506                    return -1;
5507                }
5508                break;
5509        }
5510
5511        if (mEditor != null && mEditor.mKeyListener != null) {
5512            resetErrorChangedFlag();
5513
5514            boolean doDown = true;
5515            if (otherEvent != null) {
5516                try {
5517                    beginBatchEdit();
5518                    final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5519                            otherEvent);
5520                    hideErrorIfUnchanged();
5521                    doDown = false;
5522                    if (handled) {
5523                        return -1;
5524                    }
5525                } catch (AbstractMethodError e) {
5526                    // onKeyOther was added after 1.0, so if it isn't
5527                    // implemented we need to try to dispatch as a regular down.
5528                } finally {
5529                    endBatchEdit();
5530                }
5531            }
5532
5533            if (doDown) {
5534                beginBatchEdit();
5535                final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5536                        keyCode, event);
5537                endBatchEdit();
5538                hideErrorIfUnchanged();
5539                if (handled) return 1;
5540            }
5541        }
5542
5543        // bug 650865: sometimes we get a key event before a layout.
5544        // don't try to move around if we don't know the layout.
5545
5546        if (mMovement != null && mLayout != null) {
5547            boolean doDown = true;
5548            if (otherEvent != null) {
5549                try {
5550                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5551                            otherEvent);
5552                    doDown = false;
5553                    if (handled) {
5554                        return -1;
5555                    }
5556                } catch (AbstractMethodError e) {
5557                    // onKeyOther was added after 1.0, so if it isn't
5558                    // implemented we need to try to dispatch as a regular down.
5559                }
5560            }
5561            if (doDown) {
5562                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
5563                    if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5564                        mPreventDefaultMovement = true;
5565                    }
5566                    return 2;
5567                }
5568            }
5569        }
5570
5571        return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
5572    }
5573
5574    /**
5575     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5576     * can be recorded.
5577     * @hide
5578     */
5579    public void resetErrorChangedFlag() {
5580        /*
5581         * Keep track of what the error was before doing the input
5582         * so that if an input filter changed the error, we leave
5583         * that error showing.  Otherwise, we take down whatever
5584         * error was showing when the user types something.
5585         */
5586        if (mEditor != null) mEditor.mErrorWasChanged = false;
5587    }
5588
5589    /**
5590     * @hide
5591     */
5592    public void hideErrorIfUnchanged() {
5593        if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
5594            setError(null, null);
5595        }
5596    }
5597
5598    @Override
5599    public boolean onKeyUp(int keyCode, KeyEvent event) {
5600        if (!isEnabled()) {
5601            return super.onKeyUp(keyCode, event);
5602        }
5603
5604        if (!KeyEvent.isModifierKey(keyCode)) {
5605            mPreventDefaultMovement = false;
5606        }
5607
5608        switch (keyCode) {
5609            case KeyEvent.KEYCODE_DPAD_CENTER:
5610                if (event.hasNoModifiers()) {
5611                    /*
5612                     * If there is a click listener, just call through to
5613                     * super, which will invoke it.
5614                     *
5615                     * If there isn't a click listener, try to show the soft
5616                     * input method.  (It will also
5617                     * call performClick(), but that won't do anything in
5618                     * this case.)
5619                     */
5620                    if (!hasOnClickListeners()) {
5621                        if (mMovement != null && mText instanceof Editable
5622                                && mLayout != null && onCheckIsTextEditor()) {
5623                            InputMethodManager imm = InputMethodManager.peekInstance();
5624                            viewClicked(imm);
5625                            if (imm != null && getShowSoftInputOnFocus()) {
5626                                imm.showSoftInput(this, 0);
5627                            }
5628                        }
5629                    }
5630                }
5631                return super.onKeyUp(keyCode, event);
5632
5633            case KeyEvent.KEYCODE_ENTER:
5634                if (event.hasNoModifiers()) {
5635                    if (mEditor != null && mEditor.mInputContentType != null
5636                            && mEditor.mInputContentType.onEditorActionListener != null
5637                            && mEditor.mInputContentType.enterDown) {
5638                        mEditor.mInputContentType.enterDown = false;
5639                        if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
5640                                this, EditorInfo.IME_NULL, event)) {
5641                            return true;
5642                        }
5643                    }
5644
5645                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5646                            || shouldAdvanceFocusOnEnter()) {
5647                        /*
5648                         * If there is a click listener, just call through to
5649                         * super, which will invoke it.
5650                         *
5651                         * If there isn't a click listener, try to advance focus,
5652                         * but still call through to super, which will reset the
5653                         * pressed state and longpress state.  (It will also
5654                         * call performClick(), but that won't do anything in
5655                         * this case.)
5656                         */
5657                        if (!hasOnClickListeners()) {
5658                            View v = focusSearch(FOCUS_DOWN);
5659
5660                            if (v != null) {
5661                                if (!v.requestFocus(FOCUS_DOWN)) {
5662                                    throw new IllegalStateException(
5663                                            "focus search returned a view " +
5664                                            "that wasn't able to take focus!");
5665                                }
5666
5667                                /*
5668                                 * Return true because we handled the key; super
5669                                 * will return false because there was no click
5670                                 * listener.
5671                                 */
5672                                super.onKeyUp(keyCode, event);
5673                                return true;
5674                            } else if ((event.getFlags()
5675                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5676                                // No target for next focus, but make sure the IME
5677                                // if this came from it.
5678                                InputMethodManager imm = InputMethodManager.peekInstance();
5679                                if (imm != null && imm.isActive(this)) {
5680                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5681                                }
5682                            }
5683                        }
5684                    }
5685                    return super.onKeyUp(keyCode, event);
5686                }
5687                break;
5688        }
5689
5690        if (mEditor != null && mEditor.mKeyListener != null)
5691            if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
5692                return true;
5693
5694        if (mMovement != null && mLayout != null)
5695            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5696                return true;
5697
5698        return super.onKeyUp(keyCode, event);
5699    }
5700
5701    @Override
5702    public boolean onCheckIsTextEditor() {
5703        return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
5704    }
5705
5706    @Override
5707    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5708        if (onCheckIsTextEditor() && isEnabled()) {
5709            mEditor.createInputMethodStateIfNeeded();
5710            outAttrs.inputType = getInputType();
5711            if (mEditor.mInputContentType != null) {
5712                outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5713                outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5714                outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5715                outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5716                outAttrs.extras = mEditor.mInputContentType.extras;
5717            } else {
5718                outAttrs.imeOptions = EditorInfo.IME_NULL;
5719            }
5720            if (focusSearch(FOCUS_DOWN) != null) {
5721                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5722            }
5723            if (focusSearch(FOCUS_UP) != null) {
5724                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5725            }
5726            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5727                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
5728                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
5729                    // An action has not been set, but the enter key will move to
5730                    // the next focus, so set the action to that.
5731                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
5732                } else {
5733                    // An action has not been set, and there is no focus to move
5734                    // to, so let's just supply a "done" action.
5735                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
5736                }
5737                if (!shouldAdvanceFocusOnEnter()) {
5738                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5739                }
5740            }
5741            if (isMultilineInputType(outAttrs.inputType)) {
5742                // Multi-line text editors should always show an enter key.
5743                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5744            }
5745            outAttrs.hintText = mHint;
5746            if (mText instanceof Editable) {
5747                InputConnection ic = new EditableInputConnection(this);
5748                outAttrs.initialSelStart = getSelectionStart();
5749                outAttrs.initialSelEnd = getSelectionEnd();
5750                outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
5751                return ic;
5752            }
5753        }
5754        return null;
5755    }
5756
5757    /**
5758     * If this TextView contains editable content, extract a portion of it
5759     * based on the information in <var>request</var> in to <var>outText</var>.
5760     * @return Returns true if the text was successfully extracted, else false.
5761     */
5762    public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
5763        createEditorIfNeeded();
5764        return mEditor.extractText(request, outText);
5765    }
5766
5767    /**
5768     * This is used to remove all style-impacting spans from text before new
5769     * extracted text is being replaced into it, so that we don't have any
5770     * lingering spans applied during the replace.
5771     */
5772    static void removeParcelableSpans(Spannable spannable, int start, int end) {
5773        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5774        int i = spans.length;
5775        while (i > 0) {
5776            i--;
5777            spannable.removeSpan(spans[i]);
5778        }
5779    }
5780
5781    /**
5782     * Apply to this text view the given extracted text, as previously
5783     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5784     */
5785    public void setExtractedText(ExtractedText text) {
5786        Editable content = getEditableText();
5787        if (text.text != null) {
5788            if (content == null) {
5789                setText(text.text, TextView.BufferType.EDITABLE);
5790            } else if (text.partialStartOffset < 0) {
5791                removeParcelableSpans(content, 0, content.length());
5792                content.replace(0, content.length(), text.text);
5793            } else {
5794                final int N = content.length();
5795                int start = text.partialStartOffset;
5796                if (start > N) start = N;
5797                int end = text.partialEndOffset;
5798                if (end > N) end = N;
5799                removeParcelableSpans(content, start, end);
5800                content.replace(start, end, text.text);
5801            }
5802        }
5803
5804        // Now set the selection position...  make sure it is in range, to
5805        // avoid crashes.  If this is a partial update, it is possible that
5806        // the underlying text may have changed, causing us problems here.
5807        // Also we just don't want to trust clients to do the right thing.
5808        Spannable sp = (Spannable)getText();
5809        final int N = sp.length();
5810        int start = text.selectionStart;
5811        if (start < 0) start = 0;
5812        else if (start > N) start = N;
5813        int end = text.selectionEnd;
5814        if (end < 0) end = 0;
5815        else if (end > N) end = N;
5816        Selection.setSelection(sp, start, end);
5817
5818        // Finally, update the selection mode.
5819        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5820            MetaKeyKeyListener.startSelecting(this, sp);
5821        } else {
5822            MetaKeyKeyListener.stopSelecting(this, sp);
5823        }
5824    }
5825
5826    /**
5827     * @hide
5828     */
5829    public void setExtracting(ExtractedTextRequest req) {
5830        if (mEditor.mInputMethodState != null) {
5831            mEditor.mInputMethodState.mExtractedTextRequest = req;
5832        }
5833        // This would stop a possible selection mode, but no such mode is started in case
5834        // extracted mode will start. Some text is selected though, and will trigger an action mode
5835        // in the extracted view.
5836        mEditor.hideControllers();
5837    }
5838
5839    /**
5840     * Called by the framework in response to a text completion from
5841     * the current input method, provided by it calling
5842     * {@link InputConnection#commitCompletion
5843     * InputConnection.commitCompletion()}.  The default implementation does
5844     * nothing; text views that are supporting auto-completion should override
5845     * this to do their desired behavior.
5846     *
5847     * @param text The auto complete text the user has selected.
5848     */
5849    public void onCommitCompletion(CompletionInfo text) {
5850        // intentionally empty
5851    }
5852
5853    /**
5854     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5855     * a dictionnary) from the current input method, provided by it calling
5856     * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5857     * implementation flashes the background of the corrected word to provide feedback to the user.
5858     *
5859     * @param info The auto correct info about the text that was corrected.
5860     */
5861    public void onCommitCorrection(CorrectionInfo info) {
5862        if (mEditor != null) mEditor.onCommitCorrection(info);
5863    }
5864
5865    public void beginBatchEdit() {
5866        if (mEditor != null) mEditor.beginBatchEdit();
5867    }
5868
5869    public void endBatchEdit() {
5870        if (mEditor != null) mEditor.endBatchEdit();
5871    }
5872
5873    /**
5874     * Called by the framework in response to a request to begin a batch
5875     * of edit operations through a call to link {@link #beginBatchEdit()}.
5876     */
5877    public void onBeginBatchEdit() {
5878        // intentionally empty
5879    }
5880
5881    /**
5882     * Called by the framework in response to a request to end a batch
5883     * of edit operations through a call to link {@link #endBatchEdit}.
5884     */
5885    public void onEndBatchEdit() {
5886        // intentionally empty
5887    }
5888
5889    /**
5890     * Called by the framework in response to a private command from the
5891     * current method, provided by it calling
5892     * {@link InputConnection#performPrivateCommand
5893     * InputConnection.performPrivateCommand()}.
5894     *
5895     * @param action The action name of the command.
5896     * @param data Any additional data for the command.  This may be null.
5897     * @return Return true if you handled the command, else false.
5898     */
5899    public boolean onPrivateIMECommand(String action, Bundle data) {
5900        return false;
5901    }
5902
5903    private void nullLayouts() {
5904        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5905            mSavedLayout = (BoringLayout) mLayout;
5906        }
5907        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5908            mSavedHintLayout = (BoringLayout) mHintLayout;
5909        }
5910
5911        mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
5912
5913        mBoring = mHintBoring = null;
5914
5915        // Since it depends on the value of mLayout
5916        if (mEditor != null) mEditor.prepareCursorControllers();
5917    }
5918
5919    /**
5920     * Make a new Layout based on the already-measured size of the view,
5921     * on the assumption that it was measured correctly at some point.
5922     */
5923    private void assumeLayout() {
5924        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5925
5926        if (width < 1) {
5927            width = 0;
5928        }
5929
5930        int physicalWidth = width;
5931
5932        if (mHorizontallyScrolling) {
5933            width = VERY_WIDE;
5934        }
5935
5936        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5937                      physicalWidth, false);
5938    }
5939
5940    private Layout.Alignment getLayoutAlignment() {
5941        Layout.Alignment alignment;
5942        switch (getTextAlignment()) {
5943            case TEXT_ALIGNMENT_GRAVITY:
5944                switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5945                    case Gravity.START:
5946                        alignment = Layout.Alignment.ALIGN_NORMAL;
5947                        break;
5948                    case Gravity.END:
5949                        alignment = Layout.Alignment.ALIGN_OPPOSITE;
5950                        break;
5951                    case Gravity.LEFT:
5952                        alignment = Layout.Alignment.ALIGN_LEFT;
5953                        break;
5954                    case Gravity.RIGHT:
5955                        alignment = Layout.Alignment.ALIGN_RIGHT;
5956                        break;
5957                    case Gravity.CENTER_HORIZONTAL:
5958                        alignment = Layout.Alignment.ALIGN_CENTER;
5959                        break;
5960                    default:
5961                        alignment = Layout.Alignment.ALIGN_NORMAL;
5962                        break;
5963                }
5964                break;
5965            case TEXT_ALIGNMENT_TEXT_START:
5966                alignment = Layout.Alignment.ALIGN_NORMAL;
5967                break;
5968            case TEXT_ALIGNMENT_TEXT_END:
5969                alignment = Layout.Alignment.ALIGN_OPPOSITE;
5970                break;
5971            case TEXT_ALIGNMENT_CENTER:
5972                alignment = Layout.Alignment.ALIGN_CENTER;
5973                break;
5974            case TEXT_ALIGNMENT_VIEW_START:
5975                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5976                        Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5977                break;
5978            case TEXT_ALIGNMENT_VIEW_END:
5979                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5980                        Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5981                break;
5982            case TEXT_ALIGNMENT_INHERIT:
5983                // This should never happen as we have already resolved the text alignment
5984                // but better safe than sorry so we just fall through
5985            default:
5986                alignment = Layout.Alignment.ALIGN_NORMAL;
5987                break;
5988        }
5989        return alignment;
5990    }
5991
5992    /**
5993     * The width passed in is now the desired layout width,
5994     * not the full view width with padding.
5995     * {@hide}
5996     */
5997    protected void makeNewLayout(int wantWidth, int hintWidth,
5998                                 BoringLayout.Metrics boring,
5999                                 BoringLayout.Metrics hintBoring,
6000                                 int ellipsisWidth, boolean bringIntoView) {
6001        stopMarquee();
6002
6003        // Update "old" cached values
6004        mOldMaximum = mMaximum;
6005        mOldMaxMode = mMaxMode;
6006
6007        mHighlightPathBogus = true;
6008
6009        if (wantWidth < 0) {
6010            wantWidth = 0;
6011        }
6012        if (hintWidth < 0) {
6013            hintWidth = 0;
6014        }
6015
6016        Layout.Alignment alignment = getLayoutAlignment();
6017        final boolean testDirChange = mSingleLine && mLayout != null &&
6018            (alignment == Layout.Alignment.ALIGN_NORMAL ||
6019             alignment == Layout.Alignment.ALIGN_OPPOSITE);
6020        int oldDir = 0;
6021        if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6022        boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6023        final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6024                mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6025        TruncateAt effectiveEllipsize = mEllipsize;
6026        if (mEllipsize == TruncateAt.MARQUEE &&
6027                mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6028            effectiveEllipsize = TruncateAt.END_SMALL;
6029        }
6030
6031        if (mTextDir == null) {
6032            mTextDir = getTextDirectionHeuristic();
6033        }
6034
6035        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6036                effectiveEllipsize, effectiveEllipsize == mEllipsize);
6037        if (switchEllipsize) {
6038            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6039                    TruncateAt.END : TruncateAt.MARQUEE;
6040            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6041                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6042        }
6043
6044        shouldEllipsize = mEllipsize != null;
6045        mHintLayout = null;
6046
6047        if (mHint != null) {
6048            if (shouldEllipsize) hintWidth = wantWidth;
6049
6050            if (hintBoring == UNKNOWN_BORING) {
6051                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6052                                                   mHintBoring);
6053                if (hintBoring != null) {
6054                    mHintBoring = hintBoring;
6055                }
6056            }
6057
6058            if (hintBoring != null) {
6059                if (hintBoring.width <= hintWidth &&
6060                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6061                    if (mSavedHintLayout != null) {
6062                        mHintLayout = mSavedHintLayout.
6063                                replaceOrMake(mHint, mTextPaint,
6064                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6065                                hintBoring, mIncludePad);
6066                    } else {
6067                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6068                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6069                                hintBoring, mIncludePad);
6070                    }
6071
6072                    mSavedHintLayout = (BoringLayout) mHintLayout;
6073                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6074                    if (mSavedHintLayout != null) {
6075                        mHintLayout = mSavedHintLayout.
6076                                replaceOrMake(mHint, mTextPaint,
6077                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6078                                hintBoring, mIncludePad, mEllipsize,
6079                                ellipsisWidth);
6080                    } else {
6081                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6082                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6083                                hintBoring, mIncludePad, mEllipsize,
6084                                ellipsisWidth);
6085                    }
6086                } else if (shouldEllipsize) {
6087                    mHintLayout = new StaticLayout(mHint,
6088                                0, mHint.length(),
6089                                mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6090                                mSpacingAdd, mIncludePad, mEllipsize,
6091                                ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6092                } else {
6093                    mHintLayout = new StaticLayout(mHint, mTextPaint,
6094                            hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6095                            mIncludePad);
6096                }
6097            } else if (shouldEllipsize) {
6098                mHintLayout = new StaticLayout(mHint,
6099                            0, mHint.length(),
6100                            mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6101                            mSpacingAdd, mIncludePad, mEllipsize,
6102                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6103            } else {
6104                mHintLayout = new StaticLayout(mHint, mTextPaint,
6105                        hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6106                        mIncludePad);
6107            }
6108        }
6109
6110        if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6111            registerForPreDraw();
6112        }
6113
6114        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6115            if (!compressText(ellipsisWidth)) {
6116                final int height = mLayoutParams.height;
6117                // If the size of the view does not depend on the size of the text, try to
6118                // start the marquee immediately
6119                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6120                    startMarquee();
6121                } else {
6122                    // Defer the start of the marquee until we know our width (see setFrame())
6123                    mRestartMarquee = true;
6124                }
6125            }
6126        }
6127
6128        // CursorControllers need a non-null mLayout
6129        if (mEditor != null) mEditor.prepareCursorControllers();
6130    }
6131
6132    private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6133            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6134            boolean useSaved) {
6135        Layout result = null;
6136        if (mText instanceof Spannable) {
6137            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6138                    alignment, mTextDir, mSpacingMult,
6139                    mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
6140                            ellipsisWidth);
6141        } else {
6142            if (boring == UNKNOWN_BORING) {
6143                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6144                if (boring != null) {
6145                    mBoring = boring;
6146                }
6147            }
6148
6149            if (boring != null) {
6150                if (boring.width <= wantWidth &&
6151                        (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6152                    if (useSaved && mSavedLayout != null) {
6153                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6154                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6155                                boring, mIncludePad);
6156                    } else {
6157                        result = BoringLayout.make(mTransformed, mTextPaint,
6158                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6159                                boring, mIncludePad);
6160                    }
6161
6162                    if (useSaved) {
6163                        mSavedLayout = (BoringLayout) result;
6164                    }
6165                } else if (shouldEllipsize && boring.width <= wantWidth) {
6166                    if (useSaved && mSavedLayout != null) {
6167                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6168                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6169                                boring, mIncludePad, effectiveEllipsize,
6170                                ellipsisWidth);
6171                    } else {
6172                        result = BoringLayout.make(mTransformed, mTextPaint,
6173                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6174                                boring, mIncludePad, effectiveEllipsize,
6175                                ellipsisWidth);
6176                    }
6177                } else if (shouldEllipsize) {
6178                    result = new StaticLayout(mTransformed,
6179                            0, mTransformed.length(),
6180                            mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6181                            mSpacingAdd, mIncludePad, effectiveEllipsize,
6182                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6183                } else {
6184                    result = new StaticLayout(mTransformed, mTextPaint,
6185                            wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6186                            mIncludePad);
6187                }
6188            } else if (shouldEllipsize) {
6189                result = new StaticLayout(mTransformed,
6190                        0, mTransformed.length(),
6191                        mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6192                        mSpacingAdd, mIncludePad, effectiveEllipsize,
6193                        ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6194            } else {
6195                result = new StaticLayout(mTransformed, mTextPaint,
6196                        wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6197                        mIncludePad);
6198            }
6199        }
6200        return result;
6201    }
6202
6203    private boolean compressText(float width) {
6204        if (isHardwareAccelerated()) return false;
6205
6206        // Only compress the text if it hasn't been compressed by the previous pass
6207        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6208                mTextPaint.getTextScaleX() == 1.0f) {
6209            final float textWidth = mLayout.getLineWidth(0);
6210            final float overflow = (textWidth + 1.0f - width) / width;
6211            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6212                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6213                post(new Runnable() {
6214                    public void run() {
6215                        requestLayout();
6216                    }
6217                });
6218                return true;
6219            }
6220        }
6221
6222        return false;
6223    }
6224
6225    private static int desired(Layout layout) {
6226        int n = layout.getLineCount();
6227        CharSequence text = layout.getText();
6228        float max = 0;
6229
6230        // if any line was wrapped, we can't use it.
6231        // but it's ok for the last line not to have a newline
6232
6233        for (int i = 0; i < n - 1; i++) {
6234            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6235                return -1;
6236        }
6237
6238        for (int i = 0; i < n; i++) {
6239            max = Math.max(max, layout.getLineWidth(i));
6240        }
6241
6242        return (int) FloatMath.ceil(max);
6243    }
6244
6245    /**
6246     * Set whether the TextView includes extra top and bottom padding to make
6247     * room for accents that go above the normal ascent and descent.
6248     * The default is true.
6249     *
6250     * @see #getIncludeFontPadding()
6251     *
6252     * @attr ref android.R.styleable#TextView_includeFontPadding
6253     */
6254    public void setIncludeFontPadding(boolean includepad) {
6255        if (mIncludePad != includepad) {
6256            mIncludePad = includepad;
6257
6258            if (mLayout != null) {
6259                nullLayouts();
6260                requestLayout();
6261                invalidate();
6262            }
6263        }
6264    }
6265
6266    /**
6267     * Gets whether the TextView includes extra top and bottom padding to make
6268     * room for accents that go above the normal ascent and descent.
6269     *
6270     * @see #setIncludeFontPadding(boolean)
6271     *
6272     * @attr ref android.R.styleable#TextView_includeFontPadding
6273     */
6274    public boolean getIncludeFontPadding() {
6275        return mIncludePad;
6276    }
6277
6278    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6279
6280    @Override
6281    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6282        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6283        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6284        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6285        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6286
6287        int width;
6288        int height;
6289
6290        BoringLayout.Metrics boring = UNKNOWN_BORING;
6291        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6292
6293        if (mTextDir == null) {
6294            mTextDir = getTextDirectionHeuristic();
6295        }
6296
6297        int des = -1;
6298        boolean fromexisting = false;
6299
6300        if (widthMode == MeasureSpec.EXACTLY) {
6301            // Parent has told us how big to be. So be it.
6302            width = widthSize;
6303        } else {
6304            if (mLayout != null && mEllipsize == null) {
6305                des = desired(mLayout);
6306            }
6307
6308            if (des < 0) {
6309                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6310                if (boring != null) {
6311                    mBoring = boring;
6312                }
6313            } else {
6314                fromexisting = true;
6315            }
6316
6317            if (boring == null || boring == UNKNOWN_BORING) {
6318                if (des < 0) {
6319                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6320                }
6321                width = des;
6322            } else {
6323                width = boring.width;
6324            }
6325
6326            final Drawables dr = mDrawables;
6327            if (dr != null) {
6328                width = Math.max(width, dr.mDrawableWidthTop);
6329                width = Math.max(width, dr.mDrawableWidthBottom);
6330            }
6331
6332            if (mHint != null) {
6333                int hintDes = -1;
6334                int hintWidth;
6335
6336                if (mHintLayout != null && mEllipsize == null) {
6337                    hintDes = desired(mHintLayout);
6338                }
6339
6340                if (hintDes < 0) {
6341                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
6342                    if (hintBoring != null) {
6343                        mHintBoring = hintBoring;
6344                    }
6345                }
6346
6347                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6348                    if (hintDes < 0) {
6349                        hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
6350                    }
6351                    hintWidth = hintDes;
6352                } else {
6353                    hintWidth = hintBoring.width;
6354                }
6355
6356                if (hintWidth > width) {
6357                    width = hintWidth;
6358                }
6359            }
6360
6361            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6362
6363            if (mMaxWidthMode == EMS) {
6364                width = Math.min(width, mMaxWidth * getLineHeight());
6365            } else {
6366                width = Math.min(width, mMaxWidth);
6367            }
6368
6369            if (mMinWidthMode == EMS) {
6370                width = Math.max(width, mMinWidth * getLineHeight());
6371            } else {
6372                width = Math.max(width, mMinWidth);
6373            }
6374
6375            // Check against our minimum width
6376            width = Math.max(width, getSuggestedMinimumWidth());
6377
6378            if (widthMode == MeasureSpec.AT_MOST) {
6379                width = Math.min(widthSize, width);
6380            }
6381        }
6382
6383        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6384        int unpaddedWidth = want;
6385
6386        if (mHorizontallyScrolling) want = VERY_WIDE;
6387
6388        int hintWant = want;
6389        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6390
6391        if (mLayout == null) {
6392            makeNewLayout(want, hintWant, boring, hintBoring,
6393                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6394        } else {
6395            final boolean layoutChanged = (mLayout.getWidth() != want) ||
6396                    (hintWidth != hintWant) ||
6397                    (mLayout.getEllipsizedWidth() !=
6398                            width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6399
6400            final boolean widthChanged = (mHint == null) &&
6401                    (mEllipsize == null) &&
6402                    (want > mLayout.getWidth()) &&
6403                    (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6404
6405            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6406
6407            if (layoutChanged || maximumChanged) {
6408                if (!maximumChanged && widthChanged) {
6409                    mLayout.increaseWidthTo(want);
6410                } else {
6411                    makeNewLayout(want, hintWant, boring, hintBoring,
6412                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6413                }
6414            } else {
6415                // Nothing has changed
6416            }
6417        }
6418
6419        if (heightMode == MeasureSpec.EXACTLY) {
6420            // Parent has told us how big to be. So be it.
6421            height = heightSize;
6422            mDesiredHeightAtMeasure = -1;
6423        } else {
6424            int desired = getDesiredHeight();
6425
6426            height = desired;
6427            mDesiredHeightAtMeasure = desired;
6428
6429            if (heightMode == MeasureSpec.AT_MOST) {
6430                height = Math.min(desired, heightSize);
6431            }
6432        }
6433
6434        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6435        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6436            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6437        }
6438
6439        /*
6440         * We didn't let makeNewLayout() register to bring the cursor into view,
6441         * so do it here if there is any possibility that it is needed.
6442         */
6443        if (mMovement != null ||
6444            mLayout.getWidth() > unpaddedWidth ||
6445            mLayout.getHeight() > unpaddedHeight) {
6446            registerForPreDraw();
6447        } else {
6448            scrollTo(0, 0);
6449        }
6450
6451        setMeasuredDimension(width, height);
6452    }
6453
6454    private int getDesiredHeight() {
6455        return Math.max(
6456                getDesiredHeight(mLayout, true),
6457                getDesiredHeight(mHintLayout, mEllipsize != null));
6458    }
6459
6460    private int getDesiredHeight(Layout layout, boolean cap) {
6461        if (layout == null) {
6462            return 0;
6463        }
6464
6465        int linecount = layout.getLineCount();
6466        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6467        int desired = layout.getLineTop(linecount);
6468
6469        final Drawables dr = mDrawables;
6470        if (dr != null) {
6471            desired = Math.max(desired, dr.mDrawableHeightLeft);
6472            desired = Math.max(desired, dr.mDrawableHeightRight);
6473        }
6474
6475        desired += pad;
6476
6477        if (mMaxMode == LINES) {
6478            /*
6479             * Don't cap the hint to a certain number of lines.
6480             * (Do cap it, though, if we have a maximum pixel height.)
6481             */
6482            if (cap) {
6483                if (linecount > mMaximum) {
6484                    desired = layout.getLineTop(mMaximum);
6485
6486                    if (dr != null) {
6487                        desired = Math.max(desired, dr.mDrawableHeightLeft);
6488                        desired = Math.max(desired, dr.mDrawableHeightRight);
6489                    }
6490
6491                    desired += pad;
6492                    linecount = mMaximum;
6493                }
6494            }
6495        } else {
6496            desired = Math.min(desired, mMaximum);
6497        }
6498
6499        if (mMinMode == LINES) {
6500            if (linecount < mMinimum) {
6501                desired += getLineHeight() * (mMinimum - linecount);
6502            }
6503        } else {
6504            desired = Math.max(desired, mMinimum);
6505        }
6506
6507        // Check against our minimum height
6508        desired = Math.max(desired, getSuggestedMinimumHeight());
6509
6510        return desired;
6511    }
6512
6513    /**
6514     * Check whether a change to the existing text layout requires a
6515     * new view layout.
6516     */
6517    private void checkForResize() {
6518        boolean sizeChanged = false;
6519
6520        if (mLayout != null) {
6521            // Check if our width changed
6522            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6523                sizeChanged = true;
6524                invalidate();
6525            }
6526
6527            // Check if our height changed
6528            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6529                int desiredHeight = getDesiredHeight();
6530
6531                if (desiredHeight != this.getHeight()) {
6532                    sizeChanged = true;
6533                }
6534            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6535                if (mDesiredHeightAtMeasure >= 0) {
6536                    int desiredHeight = getDesiredHeight();
6537
6538                    if (desiredHeight != mDesiredHeightAtMeasure) {
6539                        sizeChanged = true;
6540                    }
6541                }
6542            }
6543        }
6544
6545        if (sizeChanged) {
6546            requestLayout();
6547            // caller will have already invalidated
6548        }
6549    }
6550
6551    /**
6552     * Check whether entirely new text requires a new view layout
6553     * or merely a new text layout.
6554     */
6555    private void checkForRelayout() {
6556        // If we have a fixed width, we can just swap in a new text layout
6557        // if the text height stays the same or if the view height is fixed.
6558
6559        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6560                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6561                (mHint == null || mHintLayout != null) &&
6562                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6563            // Static width, so try making a new text layout.
6564
6565            int oldht = mLayout.getHeight();
6566            int want = mLayout.getWidth();
6567            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6568
6569            /*
6570             * No need to bring the text into view, since the size is not
6571             * changing (unless we do the requestLayout(), in which case it
6572             * will happen at measure).
6573             */
6574            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6575                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6576                          false);
6577
6578            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6579                // In a fixed-height view, so use our new text layout.
6580                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6581                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6582                    invalidate();
6583                    return;
6584                }
6585
6586                // Dynamic height, but height has stayed the same,
6587                // so use our new text layout.
6588                if (mLayout.getHeight() == oldht &&
6589                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6590                    invalidate();
6591                    return;
6592                }
6593            }
6594
6595            // We lose: the height has changed and we have a dynamic height.
6596            // Request a new view layout using our new text layout.
6597            requestLayout();
6598            invalidate();
6599        } else {
6600            // Dynamic width, so we have no choice but to request a new
6601            // view layout with a new text layout.
6602            nullLayouts();
6603            requestLayout();
6604            invalidate();
6605        }
6606    }
6607
6608    @Override
6609    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6610        super.onLayout(changed, left, top, right, bottom);
6611        if (mDeferScroll >= 0) {
6612            int curs = mDeferScroll;
6613            mDeferScroll = -1;
6614            bringPointIntoView(Math.min(curs, mText.length()));
6615        }
6616    }
6617
6618    private boolean isShowingHint() {
6619        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6620    }
6621
6622    /**
6623     * Returns true if anything changed.
6624     */
6625    private boolean bringTextIntoView() {
6626        Layout layout = isShowingHint() ? mHintLayout : mLayout;
6627        int line = 0;
6628        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6629            line = layout.getLineCount() - 1;
6630        }
6631
6632        Layout.Alignment a = layout.getParagraphAlignment(line);
6633        int dir = layout.getParagraphDirection(line);
6634        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6635        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6636        int ht = layout.getHeight();
6637
6638        int scrollx, scrolly;
6639
6640        // Convert to left, center, or right alignment.
6641        if (a == Layout.Alignment.ALIGN_NORMAL) {
6642            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6643                Layout.Alignment.ALIGN_RIGHT;
6644        } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6645            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6646                Layout.Alignment.ALIGN_LEFT;
6647        }
6648
6649        if (a == Layout.Alignment.ALIGN_CENTER) {
6650            /*
6651             * Keep centered if possible, or, if it is too wide to fit,
6652             * keep leading edge in view.
6653             */
6654
6655            int left = (int) FloatMath.floor(layout.getLineLeft(line));
6656            int right = (int) FloatMath.ceil(layout.getLineRight(line));
6657
6658            if (right - left < hspace) {
6659                scrollx = (right + left) / 2 - hspace / 2;
6660            } else {
6661                if (dir < 0) {
6662                    scrollx = right - hspace;
6663                } else {
6664                    scrollx = left;
6665                }
6666            }
6667        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
6668            int right = (int) FloatMath.ceil(layout.getLineRight(line));
6669            scrollx = right - hspace;
6670        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
6671            scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
6672        }
6673
6674        if (ht < vspace) {
6675            scrolly = 0;
6676        } else {
6677            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6678                scrolly = ht - vspace;
6679            } else {
6680                scrolly = 0;
6681            }
6682        }
6683
6684        if (scrollx != mScrollX || scrolly != mScrollY) {
6685            scrollTo(scrollx, scrolly);
6686            return true;
6687        } else {
6688            return false;
6689        }
6690    }
6691
6692    /**
6693     * Move the point, specified by the offset, into the view if it is needed.
6694     * This has to be called after layout. Returns true if anything changed.
6695     */
6696    public boolean bringPointIntoView(int offset) {
6697        if (isLayoutRequested()) {
6698            mDeferScroll = offset;
6699            return false;
6700        }
6701        boolean changed = false;
6702
6703        Layout layout = isShowingHint() ? mHintLayout: mLayout;
6704
6705        if (layout == null) return changed;
6706
6707        int line = layout.getLineForOffset(offset);
6708
6709        int grav;
6710
6711        switch (layout.getParagraphAlignment(line)) {
6712            case ALIGN_LEFT:
6713                grav = 1;
6714                break;
6715            case ALIGN_RIGHT:
6716                grav = -1;
6717                break;
6718            case ALIGN_NORMAL:
6719                grav = layout.getParagraphDirection(line);
6720                break;
6721            case ALIGN_OPPOSITE:
6722                grav = -layout.getParagraphDirection(line);
6723                break;
6724            case ALIGN_CENTER:
6725            default:
6726                grav = 0;
6727                break;
6728        }
6729
6730        // We only want to clamp the cursor to fit within the layout width
6731        // in left-to-right modes, because in a right to left alignment,
6732        // we want to scroll to keep the line-right on the screen, as other
6733        // lines are likely to have text flush with the right margin, which
6734        // we want to keep visible.
6735        // A better long-term solution would probably be to measure both
6736        // the full line and a blank-trimmed version, and, for example, use
6737        // the latter measurement for centering and right alignment, but for
6738        // the time being we only implement the cursor clamping in left to
6739        // right where it is most likely to be annoying.
6740        final boolean clamped = grav > 0;
6741        // FIXME: Is it okay to truncate this, or should we round?
6742        final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
6743        final int top = layout.getLineTop(line);
6744        final int bottom = layout.getLineTop(line + 1);
6745
6746        int left = (int) FloatMath.floor(layout.getLineLeft(line));
6747        int right = (int) FloatMath.ceil(layout.getLineRight(line));
6748        int ht = layout.getHeight();
6749
6750        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6751        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6752        if (!mHorizontallyScrolling && right - left > hspace && right > x) {
6753            // If cursor has been clamped, make sure we don't scroll.
6754            right = Math.max(x, left + hspace);
6755        }
6756
6757        int hslack = (bottom - top) / 2;
6758        int vslack = hslack;
6759
6760        if (vslack > vspace / 4)
6761            vslack = vspace / 4;
6762        if (hslack > hspace / 4)
6763            hslack = hspace / 4;
6764
6765        int hs = mScrollX;
6766        int vs = mScrollY;
6767
6768        if (top - vs < vslack)
6769            vs = top - vslack;
6770        if (bottom - vs > vspace - vslack)
6771            vs = bottom - (vspace - vslack);
6772        if (ht - vs < vspace)
6773            vs = ht - vspace;
6774        if (0 - vs > 0)
6775            vs = 0;
6776
6777        if (grav != 0) {
6778            if (x - hs < hslack) {
6779                hs = x - hslack;
6780            }
6781            if (x - hs > hspace - hslack) {
6782                hs = x - (hspace - hslack);
6783            }
6784        }
6785
6786        if (grav < 0) {
6787            if (left - hs > 0)
6788                hs = left;
6789            if (right - hs < hspace)
6790                hs = right - hspace;
6791        } else if (grav > 0) {
6792            if (right - hs < hspace)
6793                hs = right - hspace;
6794            if (left - hs > 0)
6795                hs = left;
6796        } else /* grav == 0 */ {
6797            if (right - left <= hspace) {
6798                /*
6799                 * If the entire text fits, center it exactly.
6800                 */
6801                hs = left - (hspace - (right - left)) / 2;
6802            } else if (x > right - hslack) {
6803                /*
6804                 * If we are near the right edge, keep the right edge
6805                 * at the edge of the view.
6806                 */
6807                hs = right - hspace;
6808            } else if (x < left + hslack) {
6809                /*
6810                 * If we are near the left edge, keep the left edge
6811                 * at the edge of the view.
6812                 */
6813                hs = left;
6814            } else if (left > hs) {
6815                /*
6816                 * Is there whitespace visible at the left?  Fix it if so.
6817                 */
6818                hs = left;
6819            } else if (right < hs + hspace) {
6820                /*
6821                 * Is there whitespace visible at the right?  Fix it if so.
6822                 */
6823                hs = right - hspace;
6824            } else {
6825                /*
6826                 * Otherwise, float as needed.
6827                 */
6828                if (x - hs < hslack) {
6829                    hs = x - hslack;
6830                }
6831                if (x - hs > hspace - hslack) {
6832                    hs = x - (hspace - hslack);
6833                }
6834            }
6835        }
6836
6837        if (hs != mScrollX || vs != mScrollY) {
6838            if (mScroller == null) {
6839                scrollTo(hs, vs);
6840            } else {
6841                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6842                int dx = hs - mScrollX;
6843                int dy = vs - mScrollY;
6844
6845                if (duration > ANIMATED_SCROLL_GAP) {
6846                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
6847                    awakenScrollBars(mScroller.getDuration());
6848                    invalidate();
6849                } else {
6850                    if (!mScroller.isFinished()) {
6851                        mScroller.abortAnimation();
6852                    }
6853
6854                    scrollBy(dx, dy);
6855                }
6856
6857                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6858            }
6859
6860            changed = true;
6861        }
6862
6863        if (isFocused()) {
6864            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6865            // requestRectangleOnScreen() is in terms of content coordinates.
6866
6867            // The offsets here are to ensure the rectangle we are using is
6868            // within our view bounds, in case the cursor is on the far left
6869            // or right.  If it isn't withing the bounds, then this request
6870            // will be ignored.
6871            if (mTempRect == null) mTempRect = new Rect();
6872            mTempRect.set(x - 2, top, x + 2, bottom);
6873            getInterestingRect(mTempRect, line);
6874            mTempRect.offset(mScrollX, mScrollY);
6875
6876            if (requestRectangleOnScreen(mTempRect)) {
6877                changed = true;
6878            }
6879        }
6880
6881        return changed;
6882    }
6883
6884    /**
6885     * Move the cursor, if needed, so that it is at an offset that is visible
6886     * to the user.  This will not move the cursor if it represents more than
6887     * one character (a selection range).  This will only work if the
6888     * TextView contains spannable text; otherwise it will do nothing.
6889     *
6890     * @return True if the cursor was actually moved, false otherwise.
6891     */
6892    public boolean moveCursorToVisibleOffset() {
6893        if (!(mText instanceof Spannable)) {
6894            return false;
6895        }
6896        int start = getSelectionStart();
6897        int end = getSelectionEnd();
6898        if (start != end) {
6899            return false;
6900        }
6901
6902        // First: make sure the line is visible on screen:
6903
6904        int line = mLayout.getLineForOffset(start);
6905
6906        final int top = mLayout.getLineTop(line);
6907        final int bottom = mLayout.getLineTop(line + 1);
6908        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6909        int vslack = (bottom - top) / 2;
6910        if (vslack > vspace / 4)
6911            vslack = vspace / 4;
6912        final int vs = mScrollY;
6913
6914        if (top < (vs+vslack)) {
6915            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6916        } else if (bottom > (vspace+vs-vslack)) {
6917            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6918        }
6919
6920        // Next: make sure the character is visible on screen:
6921
6922        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6923        final int hs = mScrollX;
6924        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6925        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
6926
6927        // line might contain bidirectional text
6928        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6929        final int highChar = leftChar > rightChar ? leftChar : rightChar;
6930
6931        int newStart = start;
6932        if (newStart < lowChar) {
6933            newStart = lowChar;
6934        } else if (newStart > highChar) {
6935            newStart = highChar;
6936        }
6937
6938        if (newStart != start) {
6939            Selection.setSelection((Spannable)mText, newStart);
6940            return true;
6941        }
6942
6943        return false;
6944    }
6945
6946    @Override
6947    public void computeScroll() {
6948        if (mScroller != null) {
6949            if (mScroller.computeScrollOffset()) {
6950                mScrollX = mScroller.getCurrX();
6951                mScrollY = mScroller.getCurrY();
6952                invalidateParentCaches();
6953                postInvalidate();  // So we draw again
6954            }
6955        }
6956    }
6957
6958    private void getInterestingRect(Rect r, int line) {
6959        convertFromViewportToContentCoordinates(r);
6960
6961        // Rectangle can can be expanded on first and last line to take
6962        // padding into account.
6963        // TODO Take left/right padding into account too?
6964        if (line == 0) r.top -= getExtendedPaddingTop();
6965        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6966    }
6967
6968    private void convertFromViewportToContentCoordinates(Rect r) {
6969        final int horizontalOffset = viewportToContentHorizontalOffset();
6970        r.left += horizontalOffset;
6971        r.right += horizontalOffset;
6972
6973        final int verticalOffset = viewportToContentVerticalOffset();
6974        r.top += verticalOffset;
6975        r.bottom += verticalOffset;
6976    }
6977
6978    int viewportToContentHorizontalOffset() {
6979        return getCompoundPaddingLeft() - mScrollX;
6980    }
6981
6982    int viewportToContentVerticalOffset() {
6983        int offset = getExtendedPaddingTop() - mScrollY;
6984        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6985            offset += getVerticalOffset(false);
6986        }
6987        return offset;
6988    }
6989
6990    @Override
6991    public void debug(int depth) {
6992        super.debug(depth);
6993
6994        String output = debugIndent(depth);
6995        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6996                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6997                + "} ";
6998
6999        if (mText != null) {
7000
7001            output += "mText=\"" + mText + "\" ";
7002            if (mLayout != null) {
7003                output += "mLayout width=" + mLayout.getWidth()
7004                        + " height=" + mLayout.getHeight();
7005            }
7006        } else {
7007            output += "mText=NULL";
7008        }
7009        Log.d(VIEW_LOG_TAG, output);
7010    }
7011
7012    /**
7013     * Convenience for {@link Selection#getSelectionStart}.
7014     */
7015    @ViewDebug.ExportedProperty(category = "text")
7016    public int getSelectionStart() {
7017        return Selection.getSelectionStart(getText());
7018    }
7019
7020    /**
7021     * Convenience for {@link Selection#getSelectionEnd}.
7022     */
7023    @ViewDebug.ExportedProperty(category = "text")
7024    public int getSelectionEnd() {
7025        return Selection.getSelectionEnd(getText());
7026    }
7027
7028    /**
7029     * Return true iff there is a selection inside this text view.
7030     */
7031    public boolean hasSelection() {
7032        final int selectionStart = getSelectionStart();
7033        final int selectionEnd = getSelectionEnd();
7034
7035        return selectionStart >= 0 && selectionStart != selectionEnd;
7036    }
7037
7038    /**
7039     * Sets the properties of this field (lines, horizontally scrolling,
7040     * transformation method) to be for a single-line input.
7041     *
7042     * @attr ref android.R.styleable#TextView_singleLine
7043     */
7044    public void setSingleLine() {
7045        setSingleLine(true);
7046    }
7047
7048    /**
7049     * Sets the properties of this field to transform input to ALL CAPS
7050     * display. This may use a "small caps" formatting if available.
7051     * This setting will be ignored if this field is editable or selectable.
7052     *
7053     * This call replaces the current transformation method. Disabling this
7054     * will not necessarily restore the previous behavior from before this
7055     * was enabled.
7056     *
7057     * @see #setTransformationMethod(TransformationMethod)
7058     * @attr ref android.R.styleable#TextView_textAllCaps
7059     */
7060    public void setAllCaps(boolean allCaps) {
7061        if (allCaps) {
7062            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7063        } else {
7064            setTransformationMethod(null);
7065        }
7066    }
7067
7068    /**
7069     * If true, sets the properties of this field (number of lines, horizontally scrolling,
7070     * transformation method) to be for a single-line input; if false, restores these to the default
7071     * conditions.
7072     *
7073     * Note that the default conditions are not necessarily those that were in effect prior this
7074     * method, and you may want to reset these properties to your custom values.
7075     *
7076     * @attr ref android.R.styleable#TextView_singleLine
7077     */
7078    @android.view.RemotableViewMethod
7079    public void setSingleLine(boolean singleLine) {
7080        // Could be used, but may break backward compatibility.
7081        // if (mSingleLine == singleLine) return;
7082        setInputTypeSingleLine(singleLine);
7083        applySingleLine(singleLine, true, true);
7084    }
7085
7086    /**
7087     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7088     * @param singleLine
7089     */
7090    private void setInputTypeSingleLine(boolean singleLine) {
7091        if (mEditor != null &&
7092                (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7093            if (singleLine) {
7094                mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7095            } else {
7096                mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7097            }
7098        }
7099    }
7100
7101    private void applySingleLine(boolean singleLine, boolean applyTransformation,
7102            boolean changeMaxLines) {
7103        mSingleLine = singleLine;
7104        if (singleLine) {
7105            setLines(1);
7106            setHorizontallyScrolling(true);
7107            if (applyTransformation) {
7108                setTransformationMethod(SingleLineTransformationMethod.getInstance());
7109            }
7110        } else {
7111            if (changeMaxLines) {
7112                setMaxLines(Integer.MAX_VALUE);
7113            }
7114            setHorizontallyScrolling(false);
7115            if (applyTransformation) {
7116                setTransformationMethod(null);
7117            }
7118        }
7119    }
7120
7121    /**
7122     * Causes words in the text that are longer than the view is wide
7123     * to be ellipsized instead of broken in the middle.  You may also
7124     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7125     * to constrain the text to a single line.  Use <code>null</code>
7126     * to turn off ellipsizing.
7127     *
7128     * If {@link #setMaxLines} has been used to set two or more lines,
7129     * {@link android.text.TextUtils.TruncateAt#END} and
7130     * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7131     * (other ellipsizing types will not do anything).
7132     *
7133     * @attr ref android.R.styleable#TextView_ellipsize
7134     */
7135    public void setEllipsize(TextUtils.TruncateAt where) {
7136        // TruncateAt is an enum. != comparison is ok between these singleton objects.
7137        if (mEllipsize != where) {
7138            mEllipsize = where;
7139
7140            if (mLayout != null) {
7141                nullLayouts();
7142                requestLayout();
7143                invalidate();
7144            }
7145        }
7146    }
7147
7148    /**
7149     * Sets how many times to repeat the marquee animation. Only applied if the
7150     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7151     *
7152     * @see #getMarqueeRepeatLimit()
7153     *
7154     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7155     */
7156    public void setMarqueeRepeatLimit(int marqueeLimit) {
7157        mMarqueeRepeatLimit = marqueeLimit;
7158    }
7159
7160    /**
7161     * Gets the number of times the marquee animation is repeated. Only meaningful if the
7162     * TextView has marquee enabled.
7163     *
7164     * @return the number of times the marquee animation is repeated. -1 if the animation
7165     * repeats indefinitely
7166     *
7167     * @see #setMarqueeRepeatLimit(int)
7168     *
7169     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7170     */
7171    public int getMarqueeRepeatLimit() {
7172        return mMarqueeRepeatLimit;
7173    }
7174
7175    /**
7176     * Returns where, if anywhere, words that are longer than the view
7177     * is wide should be ellipsized.
7178     */
7179    @ViewDebug.ExportedProperty
7180    public TextUtils.TruncateAt getEllipsize() {
7181        return mEllipsize;
7182    }
7183
7184    /**
7185     * Set the TextView so that when it takes focus, all the text is
7186     * selected.
7187     *
7188     * @attr ref android.R.styleable#TextView_selectAllOnFocus
7189     */
7190    @android.view.RemotableViewMethod
7191    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7192        createEditorIfNeeded();
7193        mEditor.mSelectAllOnFocus = selectAllOnFocus;
7194
7195        if (selectAllOnFocus && !(mText instanceof Spannable)) {
7196            setText(mText, BufferType.SPANNABLE);
7197        }
7198    }
7199
7200    /**
7201     * Set whether the cursor is visible. The default is true. Note that this property only
7202     * makes sense for editable TextView.
7203     *
7204     * @see #isCursorVisible()
7205     *
7206     * @attr ref android.R.styleable#TextView_cursorVisible
7207     */
7208    @android.view.RemotableViewMethod
7209    public void setCursorVisible(boolean visible) {
7210        if (visible && mEditor == null) return; // visible is the default value with no edit data
7211        createEditorIfNeeded();
7212        if (mEditor.mCursorVisible != visible) {
7213            mEditor.mCursorVisible = visible;
7214            invalidate();
7215
7216            mEditor.makeBlink();
7217
7218            // InsertionPointCursorController depends on mCursorVisible
7219            mEditor.prepareCursorControllers();
7220        }
7221    }
7222
7223    /**
7224     * @return whether or not the cursor is visible (assuming this TextView is editable)
7225     *
7226     * @see #setCursorVisible(boolean)
7227     *
7228     * @attr ref android.R.styleable#TextView_cursorVisible
7229     */
7230    public boolean isCursorVisible() {
7231        // true is the default value
7232        return mEditor == null ? true : mEditor.mCursorVisible;
7233    }
7234
7235    private boolean canMarquee() {
7236        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7237        return width > 0 && (mLayout.getLineWidth(0) > width ||
7238                (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7239                        mSavedMarqueeModeLayout.getLineWidth(0) > width));
7240    }
7241
7242    private void startMarquee() {
7243        // Do not ellipsize EditText
7244        if (getKeyListener() != null) return;
7245
7246        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7247            return;
7248        }
7249
7250        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7251                getLineCount() == 1 && canMarquee()) {
7252
7253            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7254                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7255                final Layout tmp = mLayout;
7256                mLayout = mSavedMarqueeModeLayout;
7257                mSavedMarqueeModeLayout = tmp;
7258                setHorizontalFadingEdgeEnabled(true);
7259                requestLayout();
7260                invalidate();
7261            }
7262
7263            if (mMarquee == null) mMarquee = new Marquee(this);
7264            mMarquee.start(mMarqueeRepeatLimit);
7265        }
7266    }
7267
7268    private void stopMarquee() {
7269        if (mMarquee != null && !mMarquee.isStopped()) {
7270            mMarquee.stop();
7271        }
7272
7273        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7274            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7275            final Layout tmp = mSavedMarqueeModeLayout;
7276            mSavedMarqueeModeLayout = mLayout;
7277            mLayout = tmp;
7278            setHorizontalFadingEdgeEnabled(false);
7279            requestLayout();
7280            invalidate();
7281        }
7282    }
7283
7284    private void startStopMarquee(boolean start) {
7285        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7286            if (start) {
7287                startMarquee();
7288            } else {
7289                stopMarquee();
7290            }
7291        }
7292    }
7293
7294    /**
7295     * This method is called when the text is changed, in case any subclasses
7296     * would like to know.
7297     *
7298     * Within <code>text</code>, the <code>lengthAfter</code> characters
7299     * beginning at <code>start</code> have just replaced old text that had
7300     * length <code>lengthBefore</code>. It is an error to attempt to make
7301     * changes to <code>text</code> from this callback.
7302     *
7303     * @param text The text the TextView is displaying
7304     * @param start The offset of the start of the range of the text that was
7305     * modified
7306     * @param lengthBefore The length of the former text that has been replaced
7307     * @param lengthAfter The length of the replacement modified text
7308     */
7309    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7310        // intentionally empty, template pattern method can be overridden by subclasses
7311    }
7312
7313    /**
7314     * This method is called when the selection has changed, in case any
7315     * subclasses would like to know.
7316     *
7317     * @param selStart The new selection start location.
7318     * @param selEnd The new selection end location.
7319     */
7320    protected void onSelectionChanged(int selStart, int selEnd) {
7321        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7322    }
7323
7324    /**
7325     * Adds a TextWatcher to the list of those whose methods are called
7326     * whenever this TextView's text changes.
7327     * <p>
7328     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7329     * not called after {@link #setText} calls.  Now, doing {@link #setText}
7330     * if there are any text changed listeners forces the buffer type to
7331     * Editable if it would not otherwise be and does call this method.
7332     */
7333    public void addTextChangedListener(TextWatcher watcher) {
7334        if (mListeners == null) {
7335            mListeners = new ArrayList<TextWatcher>();
7336        }
7337
7338        mListeners.add(watcher);
7339    }
7340
7341    /**
7342     * Removes the specified TextWatcher from the list of those whose
7343     * methods are called
7344     * whenever this TextView's text changes.
7345     */
7346    public void removeTextChangedListener(TextWatcher watcher) {
7347        if (mListeners != null) {
7348            int i = mListeners.indexOf(watcher);
7349
7350            if (i >= 0) {
7351                mListeners.remove(i);
7352            }
7353        }
7354    }
7355
7356    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7357        if (mListeners != null) {
7358            final ArrayList<TextWatcher> list = mListeners;
7359            final int count = list.size();
7360            for (int i = 0; i < count; i++) {
7361                list.get(i).beforeTextChanged(text, start, before, after);
7362            }
7363        }
7364
7365        // The spans that are inside or intersect the modified region no longer make sense
7366        removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7367        removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
7368    }
7369
7370    // Removes all spans that are inside or actually overlap the start..end range
7371    private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
7372        if (!(mText instanceof Editable)) return;
7373        Editable text = (Editable) mText;
7374
7375        T[] spans = text.getSpans(start, end, type);
7376        final int length = spans.length;
7377        for (int i = 0; i < length; i++) {
7378            final int spanStart = text.getSpanStart(spans[i]);
7379            final int spanEnd = text.getSpanEnd(spans[i]);
7380            if (spanEnd == start || spanStart == end) break;
7381            text.removeSpan(spans[i]);
7382        }
7383    }
7384
7385    void removeAdjacentSuggestionSpans(final int pos) {
7386        if (!(mText instanceof Editable)) return;
7387        final Editable text = (Editable) mText;
7388
7389        final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7390        final int length = spans.length;
7391        for (int i = 0; i < length; i++) {
7392            final int spanStart = text.getSpanStart(spans[i]);
7393            final int spanEnd = text.getSpanEnd(spans[i]);
7394            if (spanEnd == pos || spanStart == pos) {
7395                if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7396                    text.removeSpan(spans[i]);
7397                }
7398            }
7399        }
7400    }
7401
7402    /**
7403     * Not private so it can be called from an inner class without going
7404     * through a thunk.
7405     */
7406    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7407        if (mListeners != null) {
7408            final ArrayList<TextWatcher> list = mListeners;
7409            final int count = list.size();
7410            for (int i = 0; i < count; i++) {
7411                list.get(i).onTextChanged(text, start, before, after);
7412            }
7413        }
7414
7415        if (mEditor != null) mEditor.sendOnTextChanged(start, after);
7416    }
7417
7418    /**
7419     * Not private so it can be called from an inner class without going
7420     * through a thunk.
7421     */
7422    void sendAfterTextChanged(Editable text) {
7423        if (mListeners != null) {
7424            final ArrayList<TextWatcher> list = mListeners;
7425            final int count = list.size();
7426            for (int i = 0; i < count; i++) {
7427                list.get(i).afterTextChanged(text);
7428            }
7429        }
7430    }
7431
7432    void updateAfterEdit() {
7433        invalidate();
7434        int curs = getSelectionStart();
7435
7436        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7437            registerForPreDraw();
7438        }
7439
7440        checkForResize();
7441
7442        if (curs >= 0) {
7443            mHighlightPathBogus = true;
7444            if (mEditor != null) mEditor.makeBlink();
7445            bringPointIntoView(curs);
7446        }
7447    }
7448
7449    /**
7450     * Not private so it can be called from an inner class without going
7451     * through a thunk.
7452     */
7453    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
7454        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
7455        if (ims == null || ims.mBatchEditNesting == 0) {
7456            updateAfterEdit();
7457        }
7458        if (ims != null) {
7459            ims.mContentChanged = true;
7460            if (ims.mChangedStart < 0) {
7461                ims.mChangedStart = start;
7462                ims.mChangedEnd = start+before;
7463            } else {
7464                ims.mChangedStart = Math.min(ims.mChangedStart, start);
7465                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
7466            }
7467            ims.mChangedDelta += after-before;
7468        }
7469
7470        sendOnTextChanged(buffer, start, before, after);
7471        onTextChanged(buffer, start, before, after);
7472    }
7473
7474    /**
7475     * Not private so it can be called from an inner class without going
7476     * through a thunk.
7477     */
7478    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
7479        // XXX Make the start and end move together if this ends up
7480        // spending too much time invalidating.
7481
7482        boolean selChanged = false;
7483        int newSelStart=-1, newSelEnd=-1;
7484
7485        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
7486
7487        if (what == Selection.SELECTION_END) {
7488            selChanged = true;
7489            newSelEnd = newStart;
7490
7491            if (oldStart >= 0 || newStart >= 0) {
7492                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
7493                checkForResize();
7494                registerForPreDraw();
7495                if (mEditor != null) mEditor.makeBlink();
7496            }
7497        }
7498
7499        if (what == Selection.SELECTION_START) {
7500            selChanged = true;
7501            newSelStart = newStart;
7502
7503            if (oldStart >= 0 || newStart >= 0) {
7504                int end = Selection.getSelectionEnd(buf);
7505                invalidateCursor(end, oldStart, newStart);
7506            }
7507        }
7508
7509        if (selChanged) {
7510            mHighlightPathBogus = true;
7511            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
7512
7513            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7514                if (newSelStart < 0) {
7515                    newSelStart = Selection.getSelectionStart(buf);
7516                }
7517                if (newSelEnd < 0) {
7518                    newSelEnd = Selection.getSelectionEnd(buf);
7519                }
7520                onSelectionChanged(newSelStart, newSelEnd);
7521            }
7522        }
7523
7524        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7525                what instanceof CharacterStyle) {
7526            if (ims == null || ims.mBatchEditNesting == 0) {
7527                invalidate();
7528                mHighlightPathBogus = true;
7529                checkForResize();
7530            } else {
7531                ims.mContentChanged = true;
7532            }
7533            if (mEditor != null) {
7534                if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7535                if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
7536            }
7537        }
7538
7539        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7540            mHighlightPathBogus = true;
7541            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7542                ims.mSelectionModeChanged = true;
7543            }
7544
7545            if (Selection.getSelectionStart(buf) >= 0) {
7546                if (ims == null || ims.mBatchEditNesting == 0) {
7547                    invalidateCursor();
7548                } else {
7549                    ims.mCursorChanged = true;
7550                }
7551            }
7552        }
7553
7554        if (what instanceof ParcelableSpan) {
7555            // If this is a span that can be sent to a remote process,
7556            // the current extract editor would be interested in it.
7557            if (ims != null && ims.mExtractedTextRequest != null) {
7558                if (ims.mBatchEditNesting != 0) {
7559                    if (oldStart >= 0) {
7560                        if (ims.mChangedStart > oldStart) {
7561                            ims.mChangedStart = oldStart;
7562                        }
7563                        if (ims.mChangedStart > oldEnd) {
7564                            ims.mChangedStart = oldEnd;
7565                        }
7566                    }
7567                    if (newStart >= 0) {
7568                        if (ims.mChangedStart > newStart) {
7569                            ims.mChangedStart = newStart;
7570                        }
7571                        if (ims.mChangedStart > newEnd) {
7572                            ims.mChangedStart = newEnd;
7573                        }
7574                    }
7575                } else {
7576                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7577                            + oldStart + "-" + oldEnd + ","
7578                            + newStart + "-" + newEnd + " " + what);
7579                    ims.mContentChanged = true;
7580                }
7581            }
7582        }
7583
7584        if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7585                what instanceof SpellCheckSpan) {
7586            mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
7587        }
7588    }
7589
7590    /**
7591     * @hide
7592     */
7593    @Override
7594    public void dispatchFinishTemporaryDetach() {
7595        mDispatchTemporaryDetach = true;
7596        super.dispatchFinishTemporaryDetach();
7597        mDispatchTemporaryDetach = false;
7598    }
7599
7600    @Override
7601    public void onStartTemporaryDetach() {
7602        super.onStartTemporaryDetach();
7603        // Only track when onStartTemporaryDetach() is called directly,
7604        // usually because this instance is an editable field in a list
7605        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
7606
7607        // Tell the editor that we are temporarily detached. It can use this to preserve
7608        // selection state as needed.
7609        if (mEditor != null) mEditor.mTemporaryDetach = true;
7610    }
7611
7612    @Override
7613    public void onFinishTemporaryDetach() {
7614        super.onFinishTemporaryDetach();
7615        // Only track when onStartTemporaryDetach() is called directly,
7616        // usually because this instance is an editable field in a list
7617        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
7618        if (mEditor != null) mEditor.mTemporaryDetach = false;
7619    }
7620
7621    @Override
7622    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7623        if (mTemporaryDetach) {
7624            // If we are temporarily in the detach state, then do nothing.
7625            super.onFocusChanged(focused, direction, previouslyFocusedRect);
7626            return;
7627        }
7628
7629        if (mEditor != null) mEditor.onFocusChanged(focused, direction);
7630
7631        if (focused) {
7632            if (mText instanceof Spannable) {
7633                Spannable sp = (Spannable) mText;
7634                MetaKeyKeyListener.resetMetaState(sp);
7635            }
7636        }
7637
7638        startStopMarquee(focused);
7639
7640        if (mTransformation != null) {
7641            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
7642        }
7643
7644        super.onFocusChanged(focused, direction, previouslyFocusedRect);
7645    }
7646
7647    @Override
7648    public void onWindowFocusChanged(boolean hasWindowFocus) {
7649        super.onWindowFocusChanged(hasWindowFocus);
7650
7651        if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
7652
7653        startStopMarquee(hasWindowFocus);
7654    }
7655
7656    @Override
7657    protected void onVisibilityChanged(View changedView, int visibility) {
7658        super.onVisibilityChanged(changedView, visibility);
7659        if (mEditor != null && visibility != VISIBLE) {
7660            mEditor.hideControllers();
7661        }
7662    }
7663
7664    /**
7665     * Use {@link BaseInputConnection#removeComposingSpans
7666     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7667     * state from this text view.
7668     */
7669    public void clearComposingText() {
7670        if (mText instanceof Spannable) {
7671            BaseInputConnection.removeComposingSpans((Spannable)mText);
7672        }
7673    }
7674
7675    @Override
7676    public void setSelected(boolean selected) {
7677        boolean wasSelected = isSelected();
7678
7679        super.setSelected(selected);
7680
7681        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7682            if (selected) {
7683                startMarquee();
7684            } else {
7685                stopMarquee();
7686            }
7687        }
7688    }
7689
7690    @Override
7691    public boolean onTouchEvent(MotionEvent event) {
7692        final int action = event.getActionMasked();
7693
7694        if (mEditor != null) mEditor.onTouchEvent(event);
7695
7696        final boolean superResult = super.onTouchEvent(event);
7697
7698        /*
7699         * Don't handle the release after a long press, because it will
7700         * move the selection away from whatever the menu action was
7701         * trying to affect.
7702         */
7703        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7704            mEditor.mDiscardNextActionUp = false;
7705            return superResult;
7706        }
7707
7708        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
7709                (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
7710
7711         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7712                && mText instanceof Spannable && mLayout != null) {
7713            boolean handled = false;
7714
7715            if (mMovement != null) {
7716                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7717            }
7718
7719            final boolean textIsSelectable = isTextSelectable();
7720            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
7721                // The LinkMovementMethod which should handle taps on links has not been installed
7722                // on non editable text that support text selection.
7723                // We reproduce its behavior here to open links for these.
7724                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7725                        getSelectionEnd(), ClickableSpan.class);
7726
7727                if (links.length > 0) {
7728                    links[0].onClick(this);
7729                    handled = true;
7730                }
7731            }
7732
7733            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
7734                // Show the IME, except when selecting in read-only text.
7735                final InputMethodManager imm = InputMethodManager.peekInstance();
7736                viewClicked(imm);
7737                if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
7738                    handled |= imm != null && imm.showSoftInput(this, 0);
7739                }
7740
7741                // The above condition ensures that the mEditor is not null
7742                mEditor.onTouchUpEvent(event);
7743
7744                handled = true;
7745            }
7746
7747            if (handled) {
7748                return true;
7749            }
7750        }
7751
7752        return superResult;
7753    }
7754
7755    @Override
7756    public boolean onGenericMotionEvent(MotionEvent event) {
7757        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7758            try {
7759                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7760                    return true;
7761                }
7762            } catch (AbstractMethodError ex) {
7763                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7764                // Ignore its absence in case third party applications implemented the
7765                // interface directly.
7766            }
7767        }
7768        return super.onGenericMotionEvent(event);
7769    }
7770
7771    /**
7772     * @return True iff this TextView contains a text that can be edited, or if this is
7773     * a selectable TextView.
7774     */
7775    boolean isTextEditable() {
7776        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
7777    }
7778
7779    /**
7780     * Returns true, only while processing a touch gesture, if the initial
7781     * touch down event caused focus to move to the text view and as a result
7782     * its selection changed.  Only valid while processing the touch gesture
7783     * of interest, in an editable text view.
7784     */
7785    public boolean didTouchFocusSelect() {
7786        return mEditor != null && mEditor.mTouchFocusSelected;
7787    }
7788
7789    @Override
7790    public void cancelLongPress() {
7791        super.cancelLongPress();
7792        if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
7793    }
7794
7795    @Override
7796    public boolean onTrackballEvent(MotionEvent event) {
7797        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7798            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7799                return true;
7800            }
7801        }
7802
7803        return super.onTrackballEvent(event);
7804    }
7805
7806    public void setScroller(Scroller s) {
7807        mScroller = s;
7808    }
7809
7810    @Override
7811    protected float getLeftFadingEdgeStrength() {
7812        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7813                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7814            if (mMarquee != null && !mMarquee.isStopped()) {
7815                final Marquee marquee = mMarquee;
7816                if (marquee.shouldDrawLeftFade()) {
7817                    final float scroll = marquee.getScroll();
7818                    return scroll / getHorizontalFadingEdgeLength();
7819                } else {
7820                    return 0.0f;
7821                }
7822            } else if (getLineCount() == 1) {
7823                final int layoutDirection = getLayoutDirection();
7824                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7825                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7826                    case Gravity.LEFT:
7827                        return 0.0f;
7828                    case Gravity.RIGHT:
7829                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
7830                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
7831                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7832                    case Gravity.CENTER_HORIZONTAL:
7833                        return 0.0f;
7834                }
7835            }
7836        }
7837        return super.getLeftFadingEdgeStrength();
7838    }
7839
7840    @Override
7841    protected float getRightFadingEdgeStrength() {
7842        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7843                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7844            if (mMarquee != null && !mMarquee.isStopped()) {
7845                final Marquee marquee = mMarquee;
7846                final float maxFadeScroll = marquee.getMaxFadeScroll();
7847                final float scroll = marquee.getScroll();
7848                return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
7849            } else if (getLineCount() == 1) {
7850                final int layoutDirection = getLayoutDirection();
7851                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7852                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7853                    case Gravity.LEFT:
7854                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7855                                getCompoundPaddingRight();
7856                        final float lineWidth = mLayout.getLineWidth(0);
7857                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
7858                    case Gravity.RIGHT:
7859                        return 0.0f;
7860                    case Gravity.CENTER_HORIZONTAL:
7861                    case Gravity.FILL_HORIZONTAL:
7862                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7863                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7864                                getHorizontalFadingEdgeLength();
7865                }
7866            }
7867        }
7868        return super.getRightFadingEdgeStrength();
7869    }
7870
7871    @Override
7872    protected int computeHorizontalScrollRange() {
7873        if (mLayout != null) {
7874            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7875                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7876        }
7877
7878        return super.computeHorizontalScrollRange();
7879    }
7880
7881    @Override
7882    protected int computeVerticalScrollRange() {
7883        if (mLayout != null)
7884            return mLayout.getHeight();
7885
7886        return super.computeVerticalScrollRange();
7887    }
7888
7889    @Override
7890    protected int computeVerticalScrollExtent() {
7891        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7892    }
7893
7894    @Override
7895    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7896        super.findViewsWithText(outViews, searched, flags);
7897        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7898                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7899            String searchedLowerCase = searched.toString().toLowerCase();
7900            String textLowerCase = mText.toString().toLowerCase();
7901            if (textLowerCase.contains(searchedLowerCase)) {
7902                outViews.add(this);
7903            }
7904        }
7905    }
7906
7907    public enum BufferType {
7908        NORMAL, SPANNABLE, EDITABLE,
7909    }
7910
7911    /**
7912     * Returns the TextView_textColor attribute from the
7913     * TypedArray, if set, or the TextAppearance_textColor
7914     * from the TextView_textAppearance attribute, if TextView_textColor
7915     * was not set directly.
7916     */
7917    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7918        ColorStateList colors;
7919        colors = attrs.getColorStateList(com.android.internal.R.styleable.
7920                                         TextView_textColor);
7921
7922        if (colors == null) {
7923            int ap = attrs.getResourceId(com.android.internal.R.styleable.
7924                                         TextView_textAppearance, -1);
7925            if (ap != -1) {
7926                TypedArray appearance;
7927                appearance = context.obtainStyledAttributes(ap,
7928                                            com.android.internal.R.styleable.TextAppearance);
7929                colors = appearance.getColorStateList(com.android.internal.R.styleable.
7930                                                  TextAppearance_textColor);
7931                appearance.recycle();
7932            }
7933        }
7934
7935        return colors;
7936    }
7937
7938    /**
7939     * Returns the default color from the TextView_textColor attribute
7940     * from the AttributeSet, if set, or the default color from the
7941     * TextAppearance_textColor from the TextView_textAppearance attribute,
7942     * if TextView_textColor was not set directly.
7943     */
7944    public static int getTextColor(Context context,
7945                                   TypedArray attrs,
7946                                   int def) {
7947        ColorStateList colors = getTextColors(context, attrs);
7948
7949        if (colors == null) {
7950            return def;
7951        } else {
7952            return colors.getDefaultColor();
7953        }
7954    }
7955
7956    @Override
7957    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
7958        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7959        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7960            switch (keyCode) {
7961            case KeyEvent.KEYCODE_A:
7962                if (canSelectText()) {
7963                    return onTextContextMenuItem(ID_SELECT_ALL);
7964                }
7965                break;
7966            case KeyEvent.KEYCODE_X:
7967                if (canCut()) {
7968                    return onTextContextMenuItem(ID_CUT);
7969                }
7970                break;
7971            case KeyEvent.KEYCODE_C:
7972                if (canCopy()) {
7973                    return onTextContextMenuItem(ID_COPY);
7974                }
7975                break;
7976            case KeyEvent.KEYCODE_V:
7977                if (canPaste()) {
7978                    return onTextContextMenuItem(ID_PASTE);
7979                }
7980                break;
7981            }
7982        }
7983        return super.onKeyShortcut(keyCode, event);
7984    }
7985
7986    /**
7987     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7988     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
7989     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
7990     * sufficient.
7991     */
7992    private boolean canSelectText() {
7993        return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
7994    }
7995
7996    /**
7997     * Test based on the <i>intrinsic</i> charateristics of the TextView.
7998     * The text must be spannable and the movement method must allow for arbitary selection.
7999     *
8000     * See also {@link #canSelectText()}.
8001     */
8002    boolean textCanBeSelected() {
8003        // prepareCursorController() relies on this method.
8004        // If you change this condition, make sure prepareCursorController is called anywhere
8005        // the value of this condition might be changed.
8006        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8007        return isTextEditable() ||
8008                (isTextSelectable() && mText instanceof Spannable && isEnabled());
8009    }
8010
8011    private Locale getTextServicesLocale(boolean allowNullLocale) {
8012        // Start fetching the text services locale asynchronously.
8013        updateTextServicesLocaleAsync();
8014        // If !allowNullLocale and there is no cached text services locale, just return the default
8015        // locale.
8016        return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8017                : mCurrentSpellCheckerLocaleCache;
8018    }
8019
8020    /**
8021     * This is a temporary method. Future versions may support multi-locale text.
8022     * Caveat: This method may not return the latest text services locale, but this should be
8023     * acceptable and it's more important to make this method asynchronous.
8024     *
8025     * @return The locale that should be used for a word iterator
8026     * in this TextView, based on the current spell checker settings,
8027     * the current IME's locale, or the system default locale.
8028     * Please note that a word iterator in this TextView is different from another word iterator
8029     * used by SpellChecker.java of TextView. This method should be used for the former.
8030     * @hide
8031     */
8032    // TODO: Support multi-locale
8033    // TODO: Update the text services locale immediately after the keyboard locale is switched
8034    // by catching intent of keyboard switch event
8035    public Locale getTextServicesLocale() {
8036        return getTextServicesLocale(false /* allowNullLocale */);
8037    }
8038
8039    /**
8040     * This is a temporary method. Future versions may support multi-locale text.
8041     * Caveat: This method may not return the latest spell checker locale, but this should be
8042     * acceptable and it's more important to make this method asynchronous.
8043     *
8044     * @return The locale that should be used for a spell checker in this TextView,
8045     * based on the current spell checker settings, the current IME's locale, or the system default
8046     * locale.
8047     * @hide
8048     */
8049    public Locale getSpellCheckerLocale() {
8050        return getTextServicesLocale(true /* allowNullLocale */);
8051    }
8052
8053    private void updateTextServicesLocaleAsync() {
8054        // AsyncTask.execute() uses a serial executor which means we don't have
8055        // to lock around updateTextServicesLocaleLocked() to prevent it from
8056        // being executed n times in parallel.
8057        AsyncTask.execute(new Runnable() {
8058            @Override
8059            public void run() {
8060                updateTextServicesLocaleLocked();
8061            }
8062        });
8063    }
8064
8065    private void updateTextServicesLocaleLocked() {
8066        final TextServicesManager textServicesManager = (TextServicesManager)
8067                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8068        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8069        final Locale locale;
8070        if (subtype != null) {
8071            locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8072        } else {
8073            locale = null;
8074        }
8075        mCurrentSpellCheckerLocaleCache = locale;
8076    }
8077
8078    void onLocaleChanged() {
8079        // Will be re-created on demand in getWordIterator with the proper new locale
8080        mEditor.mWordIterator = null;
8081    }
8082
8083    /**
8084     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8085     * Made available to achieve a consistent behavior.
8086     * @hide
8087     */
8088    public WordIterator getWordIterator() {
8089        if (mEditor != null) {
8090            return mEditor.getWordIterator();
8091        } else {
8092            return null;
8093        }
8094    }
8095
8096    @Override
8097    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
8098        super.onPopulateAccessibilityEvent(event);
8099
8100        final boolean isPassword = hasPasswordTransformationMethod();
8101        if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8102            final CharSequence text = getTextForAccessibility();
8103            if (!TextUtils.isEmpty(text)) {
8104                event.getText().add(text);
8105            }
8106        }
8107    }
8108
8109    /**
8110     * @return true if the user has explicitly allowed accessibility services
8111     * to speak passwords.
8112     */
8113    private boolean shouldSpeakPasswordsForAccessibility() {
8114        return (Settings.Secure.getInt(mContext.getContentResolver(),
8115                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
8116    }
8117
8118    @Override
8119    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8120        super.onInitializeAccessibilityEvent(event);
8121
8122        event.setClassName(TextView.class.getName());
8123        final boolean isPassword = hasPasswordTransformationMethod();
8124        event.setPassword(isPassword);
8125
8126        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8127            event.setFromIndex(Selection.getSelectionStart(mText));
8128            event.setToIndex(Selection.getSelectionEnd(mText));
8129            event.setItemCount(mText.length());
8130        }
8131    }
8132
8133    @Override
8134    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8135        super.onInitializeAccessibilityNodeInfo(info);
8136
8137        info.setClassName(TextView.class.getName());
8138        final boolean isPassword = hasPasswordTransformationMethod();
8139        info.setPassword(isPassword);
8140
8141        if (!isPassword) {
8142            info.setText(getTextForAccessibility());
8143        }
8144
8145        if (mBufferType == BufferType.EDITABLE) {
8146            info.setEditable(true);
8147        }
8148
8149        if (mEditor != null) {
8150            info.setInputType(mEditor.mInputType);
8151
8152            if (mEditor.mError != null) {
8153                info.setContentInvalid(true);
8154            }
8155        }
8156
8157        if (!TextUtils.isEmpty(mText)) {
8158            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8159            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8160            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8161                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8162                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8163                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8164                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8165        }
8166
8167        if (isFocused()) {
8168            if (canSelectText()) {
8169                info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8170            }
8171            if (canCopy()) {
8172                info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8173            }
8174            if (canPaste()) {
8175                info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8176            }
8177            if (canCut()) {
8178                info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8179            }
8180        }
8181
8182        if (!isSingleLine()) {
8183            info.setMultiLine(true);
8184        }
8185    }
8186
8187    @Override
8188    public boolean performAccessibilityAction(int action, Bundle arguments) {
8189        switch (action) {
8190            case AccessibilityNodeInfo.ACTION_COPY: {
8191                if (isFocused() && canCopy()) {
8192                    if (onTextContextMenuItem(ID_COPY)) {
8193                        return true;
8194                    }
8195                }
8196            } return false;
8197            case AccessibilityNodeInfo.ACTION_PASTE: {
8198                if (isFocused() && canPaste()) {
8199                    if (onTextContextMenuItem(ID_PASTE)) {
8200                        return true;
8201                    }
8202                }
8203            } return false;
8204            case AccessibilityNodeInfo.ACTION_CUT: {
8205                if (isFocused() && canCut()) {
8206                    if (onTextContextMenuItem(ID_CUT)) {
8207                        return true;
8208                    }
8209                }
8210            } return false;
8211            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8212                if (isFocused() && canSelectText()) {
8213                    CharSequence text = getIterableTextForAccessibility();
8214                    if (text == null) {
8215                        return false;
8216                    }
8217                    final int start = (arguments != null) ? arguments.getInt(
8218                            AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8219                    final int end = (arguments != null) ? arguments.getInt(
8220                            AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8221                    if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8222                        // No arguments clears the selection.
8223                        if (start == end && end == -1) {
8224                            Selection.removeSelection((Spannable) text);
8225                            return true;
8226                        }
8227                        if (start >= 0 && start <= end && end <= text.length()) {
8228                            Selection.setSelection((Spannable) text, start, end);
8229                            // Make sure selection mode is engaged.
8230                            if (mEditor != null) {
8231                                mEditor.startSelectionActionMode();
8232                            }
8233                            return true;
8234                        }
8235                    }
8236                }
8237            } return false;
8238            default: {
8239                return super.performAccessibilityAction(action, arguments);
8240            }
8241        }
8242    }
8243
8244    @Override
8245    public void sendAccessibilityEvent(int eventType) {
8246        // Do not send scroll events since first they are not interesting for
8247        // accessibility and second such events a generated too frequently.
8248        // For details see the implementation of bringTextIntoView().
8249        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8250            return;
8251        }
8252        super.sendAccessibilityEvent(eventType);
8253    }
8254
8255    /**
8256     * Gets the text reported for accessibility purposes.
8257     *
8258     * @return The accessibility text.
8259     *
8260     * @hide
8261     */
8262    public CharSequence getTextForAccessibility() {
8263        CharSequence text = getText();
8264        if (TextUtils.isEmpty(text)) {
8265            text = getHint();
8266        }
8267        return text;
8268    }
8269
8270    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8271            int fromIndex, int removedCount, int addedCount) {
8272        AccessibilityEvent event =
8273            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8274        event.setFromIndex(fromIndex);
8275        event.setRemovedCount(removedCount);
8276        event.setAddedCount(addedCount);
8277        event.setBeforeText(beforeText);
8278        sendAccessibilityEventUnchecked(event);
8279    }
8280
8281    /**
8282     * Returns whether this text view is a current input method target.  The
8283     * default implementation just checks with {@link InputMethodManager}.
8284     */
8285    public boolean isInputMethodTarget() {
8286        InputMethodManager imm = InputMethodManager.peekInstance();
8287        return imm != null && imm.isActive(this);
8288    }
8289
8290    static final int ID_SELECT_ALL = android.R.id.selectAll;
8291    static final int ID_CUT = android.R.id.cut;
8292    static final int ID_COPY = android.R.id.copy;
8293    static final int ID_PASTE = android.R.id.paste;
8294
8295    /**
8296     * Called when a context menu option for the text view is selected.  Currently
8297     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8298     * {@link android.R.id#copy} or {@link android.R.id#paste}.
8299     *
8300     * @return true if the context menu item action was performed.
8301     */
8302    public boolean onTextContextMenuItem(int id) {
8303        int min = 0;
8304        int max = mText.length();
8305
8306        if (isFocused()) {
8307            final int selStart = getSelectionStart();
8308            final int selEnd = getSelectionEnd();
8309
8310            min = Math.max(0, Math.min(selStart, selEnd));
8311            max = Math.max(0, Math.max(selStart, selEnd));
8312        }
8313
8314        switch (id) {
8315            case ID_SELECT_ALL:
8316                // This does not enter text selection mode. Text is highlighted, so that it can be
8317                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
8318                selectAllText();
8319                return true;
8320
8321            case ID_PASTE:
8322                paste(min, max);
8323                return true;
8324
8325            case ID_CUT:
8326                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8327                deleteText_internal(min, max);
8328                stopSelectionActionMode();
8329                return true;
8330
8331            case ID_COPY:
8332                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8333                stopSelectionActionMode();
8334                return true;
8335        }
8336        return false;
8337    }
8338
8339    CharSequence getTransformedText(int start, int end) {
8340        return removeSuggestionSpans(mTransformed.subSequence(start, end));
8341    }
8342
8343    @Override
8344    public boolean performLongClick() {
8345        boolean handled = false;
8346
8347        if (super.performLongClick()) {
8348            handled = true;
8349        }
8350
8351        if (mEditor != null) {
8352            handled |= mEditor.performLongClick(handled);
8353        }
8354
8355        if (handled) {
8356            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
8357            if (mEditor != null) mEditor.mDiscardNextActionUp = true;
8358        }
8359
8360        return handled;
8361    }
8362
8363    @Override
8364    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8365        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
8366        if (mEditor != null) {
8367            mEditor.onScrollChanged();
8368        }
8369    }
8370
8371    /**
8372     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8373     * by the IME or by the spell checker as the user types. This is done by adding
8374     * {@link SuggestionSpan}s to the text.
8375     *
8376     * When suggestions are enabled (default), this list of suggestions will be displayed when the
8377     * user asks for them on these parts of the text. This value depends on the inputType of this
8378     * TextView.
8379     *
8380     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8381     *
8382     * In addition, the type variation must be one of
8383     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8384     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8385     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8386     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8387     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8388     *
8389     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8390     *
8391     * @return true if the suggestions popup window is enabled, based on the inputType.
8392     */
8393    public boolean isSuggestionsEnabled() {
8394        if (mEditor == null) return false;
8395        if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8396            return false;
8397        }
8398        if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
8399
8400        final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8401        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8402                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8403                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8404                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8405                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8406    }
8407
8408    /**
8409     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8410     * selection is initiated in this View.
8411     *
8412     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8413     * Paste actions, depending on what this View supports.
8414     *
8415     * A custom implementation can add new entries in the default menu in its
8416     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8417     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8418     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8419     * or {@link android.R.id#paste} ids as parameters.
8420     *
8421     * Returning false from
8422     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8423     * the action mode from being started.
8424     *
8425     * Action click events should be handled by the custom implementation of
8426     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8427     *
8428     * Note that text selection mode is not started when a TextView receives focus and the
8429     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8430     * that case, to allow for quick replacement.
8431     */
8432    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8433        createEditorIfNeeded();
8434        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
8435    }
8436
8437    /**
8438     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8439     *
8440     * @return The current custom selection callback.
8441     */
8442    public ActionMode.Callback getCustomSelectionActionModeCallback() {
8443        return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
8444    }
8445
8446    /**
8447     * @hide
8448     */
8449    protected void stopSelectionActionMode() {
8450        mEditor.stopSelectionActionMode();
8451    }
8452
8453    boolean canCut() {
8454        if (hasPasswordTransformationMethod()) {
8455            return false;
8456        }
8457
8458        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8459                mEditor.mKeyListener != null) {
8460            return true;
8461        }
8462
8463        return false;
8464    }
8465
8466    boolean canCopy() {
8467        if (hasPasswordTransformationMethod()) {
8468            return false;
8469        }
8470
8471        if (mText.length() > 0 && hasSelection()) {
8472            return true;
8473        }
8474
8475        return false;
8476    }
8477
8478    boolean canPaste() {
8479        return (mText instanceof Editable &&
8480                mEditor != null && mEditor.mKeyListener != null &&
8481                getSelectionStart() >= 0 &&
8482                getSelectionEnd() >= 0 &&
8483                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8484                hasPrimaryClip());
8485    }
8486
8487    boolean selectAllText() {
8488        final int length = mText.length();
8489        Selection.setSelection((Spannable) mText, 0, length);
8490        return length > 0;
8491    }
8492
8493    /**
8494     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8495     * by [min, max] when replacing this region by paste.
8496     * Note that if there were two spaces (or more) at that position before, they are kept. We just
8497     * make sure we do not add an extra one from the paste content.
8498     */
8499    long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8500        if (paste.length() > 0) {
8501            if (min > 0) {
8502                final char charBefore = mTransformed.charAt(min - 1);
8503                final char charAfter = paste.charAt(0);
8504
8505                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8506                    // Two spaces at beginning of paste: remove one
8507                    final int originalLength = mText.length();
8508                    deleteText_internal(min - 1, min);
8509                    // Due to filters, there is no guarantee that exactly one character was
8510                    // removed: count instead.
8511                    final int delta = mText.length() - originalLength;
8512                    min += delta;
8513                    max += delta;
8514                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8515                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8516                    // No space at beginning of paste: add one
8517                    final int originalLength = mText.length();
8518                    replaceText_internal(min, min, " ");
8519                    // Taking possible filters into account as above.
8520                    final int delta = mText.length() - originalLength;
8521                    min += delta;
8522                    max += delta;
8523                }
8524            }
8525
8526            if (max < mText.length()) {
8527                final char charBefore = paste.charAt(paste.length() - 1);
8528                final char charAfter = mTransformed.charAt(max);
8529
8530                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8531                    // Two spaces at end of paste: remove one
8532                    deleteText_internal(max, max + 1);
8533                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8534                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8535                    // No space at end of paste: add one
8536                    replaceText_internal(max, max, " ");
8537                }
8538            }
8539        }
8540
8541        return TextUtils.packRangeInLong(min, max);
8542    }
8543
8544    /**
8545     * Paste clipboard content between min and max positions.
8546     */
8547    private void paste(int min, int max) {
8548        ClipboardManager clipboard =
8549            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8550        ClipData clip = clipboard.getPrimaryClip();
8551        if (clip != null) {
8552            boolean didFirst = false;
8553            for (int i=0; i<clip.getItemCount(); i++) {
8554                CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
8555                if (paste != null) {
8556                    if (!didFirst) {
8557                        long minMax = prepareSpacesAroundPaste(min, max, paste);
8558                        min = TextUtils.unpackRangeStartFromLong(minMax);
8559                        max = TextUtils.unpackRangeEndFromLong(minMax);
8560                        Selection.setSelection((Spannable) mText, max);
8561                        ((Editable) mText).replace(min, max, paste);
8562                        didFirst = true;
8563                    } else {
8564                        ((Editable) mText).insert(getSelectionEnd(), "\n");
8565                        ((Editable) mText).insert(getSelectionEnd(), paste);
8566                    }
8567                }
8568            }
8569            stopSelectionActionMode();
8570            LAST_CUT_OR_COPY_TIME = 0;
8571        }
8572    }
8573
8574    private void setPrimaryClip(ClipData clip) {
8575        ClipboardManager clipboard = (ClipboardManager) getContext().
8576                getSystemService(Context.CLIPBOARD_SERVICE);
8577        clipboard.setPrimaryClip(clip);
8578        LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8579    }
8580
8581    /**
8582     * Get the character offset closest to the specified absolute position. A typical use case is to
8583     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8584     *
8585     * @param x The horizontal absolute position of a point on screen
8586     * @param y The vertical absolute position of a point on screen
8587     * @return the character offset for the character whose position is closest to the specified
8588     *  position. Returns -1 if there is no layout.
8589     */
8590    public int getOffsetForPosition(float x, float y) {
8591        if (getLayout() == null) return -1;
8592        final int line = getLineAtCoordinate(y);
8593        final int offset = getOffsetAtCoordinate(line, x);
8594        return offset;
8595    }
8596
8597    float convertToLocalHorizontalCoordinate(float x) {
8598        x -= getTotalPaddingLeft();
8599        // Clamp the position to inside of the view.
8600        x = Math.max(0.0f, x);
8601        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8602        x += getScrollX();
8603        return x;
8604    }
8605
8606    int getLineAtCoordinate(float y) {
8607        y -= getTotalPaddingTop();
8608        // Clamp the position to inside of the view.
8609        y = Math.max(0.0f, y);
8610        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8611        y += getScrollY();
8612        return getLayout().getLineForVertical((int) y);
8613    }
8614
8615    private int getOffsetAtCoordinate(int line, float x) {
8616        x = convertToLocalHorizontalCoordinate(x);
8617        return getLayout().getOffsetForHorizontal(line, x);
8618    }
8619
8620    @Override
8621    public boolean onDragEvent(DragEvent event) {
8622        switch (event.getAction()) {
8623            case DragEvent.ACTION_DRAG_STARTED:
8624                return mEditor != null && mEditor.hasInsertionController();
8625
8626            case DragEvent.ACTION_DRAG_ENTERED:
8627                TextView.this.requestFocus();
8628                return true;
8629
8630            case DragEvent.ACTION_DRAG_LOCATION:
8631                final int offset = getOffsetForPosition(event.getX(), event.getY());
8632                Selection.setSelection((Spannable)mText, offset);
8633                return true;
8634
8635            case DragEvent.ACTION_DROP:
8636                if (mEditor != null) mEditor.onDrop(event);
8637                return true;
8638
8639            case DragEvent.ACTION_DRAG_ENDED:
8640            case DragEvent.ACTION_DRAG_EXITED:
8641            default:
8642                return true;
8643        }
8644    }
8645
8646    boolean isInBatchEditMode() {
8647        if (mEditor == null) return false;
8648        final Editor.InputMethodState ims = mEditor.mInputMethodState;
8649        if (ims != null) {
8650            return ims.mBatchEditNesting > 0;
8651        }
8652        return mEditor.mInBatchEditControllers;
8653    }
8654
8655    @Override
8656    public void onRtlPropertiesChanged(int layoutDirection) {
8657        super.onRtlPropertiesChanged(layoutDirection);
8658
8659        mTextDir = getTextDirectionHeuristic();
8660    }
8661
8662    TextDirectionHeuristic getTextDirectionHeuristic() {
8663        if (hasPasswordTransformationMethod()) {
8664            // passwords fields should be LTR
8665            return TextDirectionHeuristics.LTR;
8666        }
8667
8668        // Always need to resolve layout direction first
8669        final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
8670
8671        // Now, we can select the heuristic
8672        switch (getTextDirection()) {
8673            default:
8674            case TEXT_DIRECTION_FIRST_STRONG:
8675                return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
8676                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
8677            case TEXT_DIRECTION_ANY_RTL:
8678                return TextDirectionHeuristics.ANYRTL_LTR;
8679            case TEXT_DIRECTION_LTR:
8680                return TextDirectionHeuristics.LTR;
8681            case TEXT_DIRECTION_RTL:
8682                return TextDirectionHeuristics.RTL;
8683            case TEXT_DIRECTION_LOCALE:
8684                return TextDirectionHeuristics.LOCALE;
8685        }
8686    }
8687
8688    /**
8689     * @hide
8690     */
8691    @Override
8692    public void onResolveDrawables(int layoutDirection) {
8693        // No need to resolve twice
8694        if (mLastLayoutDirection == layoutDirection) {
8695            return;
8696        }
8697        mLastLayoutDirection = layoutDirection;
8698
8699        // Resolve drawables
8700        if (mDrawables != null) {
8701            mDrawables.resolveWithLayoutDirection(layoutDirection);
8702        }
8703    }
8704
8705    /**
8706     * @hide
8707     */
8708    protected void resetResolvedDrawables() {
8709        super.resetResolvedDrawables();
8710        mLastLayoutDirection = -1;
8711    }
8712
8713    /**
8714     * @hide
8715     */
8716    protected void viewClicked(InputMethodManager imm) {
8717        if (imm != null) {
8718            imm.viewClicked(this);
8719        }
8720    }
8721
8722    /**
8723     * Deletes the range of text [start, end[.
8724     * @hide
8725     */
8726    protected void deleteText_internal(int start, int end) {
8727        ((Editable) mText).delete(start, end);
8728    }
8729
8730    /**
8731     * Replaces the range of text [start, end[ by replacement text
8732     * @hide
8733     */
8734    protected void replaceText_internal(int start, int end, CharSequence text) {
8735        ((Editable) mText).replace(start, end, text);
8736    }
8737
8738    /**
8739     * Sets a span on the specified range of text
8740     * @hide
8741     */
8742    protected void setSpan_internal(Object span, int start, int end, int flags) {
8743        ((Editable) mText).setSpan(span, start, end, flags);
8744    }
8745
8746    /**
8747     * Moves the cursor to the specified offset position in text
8748     * @hide
8749     */
8750    protected void setCursorPosition_internal(int start, int end) {
8751        Selection.setSelection(((Editable) mText), start, end);
8752    }
8753
8754    /**
8755     * An Editor should be created as soon as any of the editable-specific fields (grouped
8756     * inside the Editor object) is assigned to a non-default value.
8757     * This method will create the Editor if needed.
8758     *
8759     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8760     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8761     * Editor for backward compatibility, as soon as one of these fields is assigned.
8762     *
8763     * Also note that for performance reasons, the mEditor is created when needed, but not
8764     * reset when no more edit-specific fields are needed.
8765     */
8766    private void createEditorIfNeeded() {
8767        if (mEditor == null) {
8768            mEditor = new Editor(this);
8769        }
8770    }
8771
8772    /**
8773     * @hide
8774     */
8775    @Override
8776    public CharSequence getIterableTextForAccessibility() {
8777        if (!(mText instanceof Spannable)) {
8778            setText(mText, BufferType.SPANNABLE);
8779        }
8780        return mText;
8781    }
8782
8783    /**
8784     * @hide
8785     */
8786    @Override
8787    public TextSegmentIterator getIteratorForGranularity(int granularity) {
8788        switch (granularity) {
8789            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8790                Spannable text = (Spannable) getIterableTextForAccessibility();
8791                if (!TextUtils.isEmpty(text) && getLayout() != null) {
8792                    AccessibilityIterators.LineTextSegmentIterator iterator =
8793                        AccessibilityIterators.LineTextSegmentIterator.getInstance();
8794                    iterator.initialize(text, getLayout());
8795                    return iterator;
8796                }
8797            } break;
8798            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8799                Spannable text = (Spannable) getIterableTextForAccessibility();
8800                if (!TextUtils.isEmpty(text) && getLayout() != null) {
8801                    AccessibilityIterators.PageTextSegmentIterator iterator =
8802                        AccessibilityIterators.PageTextSegmentIterator.getInstance();
8803                    iterator.initialize(this);
8804                    return iterator;
8805                }
8806            } break;
8807        }
8808        return super.getIteratorForGranularity(granularity);
8809    }
8810
8811    /**
8812     * @hide
8813     */
8814    @Override
8815    public int getAccessibilitySelectionStart() {
8816        return getSelectionStart();
8817    }
8818
8819    /**
8820     * @hide
8821     */
8822    public boolean isAccessibilitySelectionExtendable() {
8823        return true;
8824    }
8825
8826    /**
8827     * @hide
8828     */
8829    @Override
8830    public int getAccessibilitySelectionEnd() {
8831        return getSelectionEnd();
8832    }
8833
8834    /**
8835     * @hide
8836     */
8837    @Override
8838    public void setAccessibilitySelection(int start, int end) {
8839        if (getAccessibilitySelectionStart() == start
8840                && getAccessibilitySelectionEnd() == end) {
8841            return;
8842        }
8843        // Hide all selection controllers used for adjusting selection
8844        // since we are doing so explicitlty by other means and these
8845        // controllers interact with how selection behaves.
8846        if (mEditor != null) {
8847            mEditor.hideControllers();
8848        }
8849        CharSequence text = getIterableTextForAccessibility();
8850        if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
8851            Selection.setSelection((Spannable) text, start, end);
8852        } else {
8853            Selection.removeSelection((Spannable) text);
8854        }
8855    }
8856
8857    /**
8858     * User interface state that is stored by TextView for implementing
8859     * {@link View#onSaveInstanceState}.
8860     */
8861    public static class SavedState extends BaseSavedState {
8862        int selStart;
8863        int selEnd;
8864        CharSequence text;
8865        boolean frozenWithFocus;
8866        CharSequence error;
8867
8868        SavedState(Parcelable superState) {
8869            super(superState);
8870        }
8871
8872        @Override
8873        public void writeToParcel(Parcel out, int flags) {
8874            super.writeToParcel(out, flags);
8875            out.writeInt(selStart);
8876            out.writeInt(selEnd);
8877            out.writeInt(frozenWithFocus ? 1 : 0);
8878            TextUtils.writeToParcel(text, out, flags);
8879
8880            if (error == null) {
8881                out.writeInt(0);
8882            } else {
8883                out.writeInt(1);
8884                TextUtils.writeToParcel(error, out, flags);
8885            }
8886        }
8887
8888        @Override
8889        public String toString() {
8890            String str = "TextView.SavedState{"
8891                    + Integer.toHexString(System.identityHashCode(this))
8892                    + " start=" + selStart + " end=" + selEnd;
8893            if (text != null) {
8894                str += " text=" + text;
8895            }
8896            return str + "}";
8897        }
8898
8899        @SuppressWarnings("hiding")
8900        public static final Parcelable.Creator<SavedState> CREATOR
8901                = new Parcelable.Creator<SavedState>() {
8902            public SavedState createFromParcel(Parcel in) {
8903                return new SavedState(in);
8904            }
8905
8906            public SavedState[] newArray(int size) {
8907                return new SavedState[size];
8908            }
8909        };
8910
8911        private SavedState(Parcel in) {
8912            super(in);
8913            selStart = in.readInt();
8914            selEnd = in.readInt();
8915            frozenWithFocus = (in.readInt() != 0);
8916            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8917
8918            if (in.readInt() != 0) {
8919                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8920            }
8921        }
8922    }
8923
8924    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8925        private char[] mChars;
8926        private int mStart, mLength;
8927
8928        public CharWrapper(char[] chars, int start, int len) {
8929            mChars = chars;
8930            mStart = start;
8931            mLength = len;
8932        }
8933
8934        /* package */ void set(char[] chars, int start, int len) {
8935            mChars = chars;
8936            mStart = start;
8937            mLength = len;
8938        }
8939
8940        public int length() {
8941            return mLength;
8942        }
8943
8944        public char charAt(int off) {
8945            return mChars[off + mStart];
8946        }
8947
8948        @Override
8949        public String toString() {
8950            return new String(mChars, mStart, mLength);
8951        }
8952
8953        public CharSequence subSequence(int start, int end) {
8954            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8955                throw new IndexOutOfBoundsException(start + ", " + end);
8956            }
8957
8958            return new String(mChars, start + mStart, end - start);
8959        }
8960
8961        public void getChars(int start, int end, char[] buf, int off) {
8962            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8963                throw new IndexOutOfBoundsException(start + ", " + end);
8964            }
8965
8966            System.arraycopy(mChars, start + mStart, buf, off, end - start);
8967        }
8968
8969        public void drawText(Canvas c, int start, int end,
8970                             float x, float y, Paint p) {
8971            c.drawText(mChars, start + mStart, end - start, x, y, p);
8972        }
8973
8974        public void drawTextRun(Canvas c, int start, int end,
8975                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
8976            int count = end - start;
8977            int contextCount = contextEnd - contextStart;
8978            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
8979                    contextCount, x, y, flags, p);
8980        }
8981
8982        public float measureText(int start, int end, Paint p) {
8983            return p.measureText(mChars, start + mStart, end - start);
8984        }
8985
8986        public int getTextWidths(int start, int end, float[] widths, Paint p) {
8987            return p.getTextWidths(mChars, start + mStart, end - start, widths);
8988        }
8989
8990        public float getTextRunAdvances(int start, int end, int contextStart,
8991                int contextEnd, int flags, float[] advances, int advancesIndex,
8992                Paint p) {
8993            int count = end - start;
8994            int contextCount = contextEnd - contextStart;
8995            return p.getTextRunAdvances(mChars, start + mStart, count,
8996                    contextStart + mStart, contextCount, flags, advances,
8997                    advancesIndex);
8998        }
8999
9000        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
9001                int offset, int cursorOpt, Paint p) {
9002            int contextCount = contextEnd - contextStart;
9003            return p.getTextRunCursor(mChars, contextStart + mStart,
9004                    contextCount, flags, offset + mStart, cursorOpt);
9005        }
9006    }
9007
9008    private static final class Marquee extends Handler {
9009        // TODO: Add an option to configure this
9010        private static final float MARQUEE_DELTA_MAX = 0.07f;
9011        private static final int MARQUEE_DELAY = 1200;
9012        private static final int MARQUEE_RESTART_DELAY = 1200;
9013        private static final int MARQUEE_RESOLUTION = 1000 / 30;
9014        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
9015
9016        private static final byte MARQUEE_STOPPED = 0x0;
9017        private static final byte MARQUEE_STARTING = 0x1;
9018        private static final byte MARQUEE_RUNNING = 0x2;
9019
9020        private static final int MESSAGE_START = 0x1;
9021        private static final int MESSAGE_TICK = 0x2;
9022        private static final int MESSAGE_RESTART = 0x3;
9023
9024        private final WeakReference<TextView> mView;
9025
9026        private byte mStatus = MARQUEE_STOPPED;
9027        private final float mScrollUnit;
9028        private float mMaxScroll;
9029        private float mMaxFadeScroll;
9030        private float mGhostStart;
9031        private float mGhostOffset;
9032        private float mFadeStop;
9033        private int mRepeatLimit;
9034
9035        private float mScroll;
9036
9037        Marquee(TextView v) {
9038            final float density = v.getContext().getResources().getDisplayMetrics().density;
9039            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
9040            mView = new WeakReference<TextView>(v);
9041        }
9042
9043        @Override
9044        public void handleMessage(Message msg) {
9045            switch (msg.what) {
9046                case MESSAGE_START:
9047                    mStatus = MARQUEE_RUNNING;
9048                    tick();
9049                    break;
9050                case MESSAGE_TICK:
9051                    tick();
9052                    break;
9053                case MESSAGE_RESTART:
9054                    if (mStatus == MARQUEE_RUNNING) {
9055                        if (mRepeatLimit >= 0) {
9056                            mRepeatLimit--;
9057                        }
9058                        start(mRepeatLimit);
9059                    }
9060                    break;
9061            }
9062        }
9063
9064        void tick() {
9065            if (mStatus != MARQUEE_RUNNING) {
9066                return;
9067            }
9068
9069            removeMessages(MESSAGE_TICK);
9070
9071            final TextView textView = mView.get();
9072            if (textView != null && (textView.isFocused() || textView.isSelected())) {
9073                mScroll += mScrollUnit;
9074                if (mScroll > mMaxScroll) {
9075                    mScroll = mMaxScroll;
9076                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
9077                } else {
9078                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
9079                }
9080                textView.invalidate();
9081            }
9082        }
9083
9084        void stop() {
9085            mStatus = MARQUEE_STOPPED;
9086            removeMessages(MESSAGE_START);
9087            removeMessages(MESSAGE_RESTART);
9088            removeMessages(MESSAGE_TICK);
9089            resetScroll();
9090        }
9091
9092        private void resetScroll() {
9093            mScroll = 0.0f;
9094            final TextView textView = mView.get();
9095            if (textView != null) textView.invalidate();
9096        }
9097
9098        void start(int repeatLimit) {
9099            if (repeatLimit == 0) {
9100                stop();
9101                return;
9102            }
9103            mRepeatLimit = repeatLimit;
9104            final TextView textView = mView.get();
9105            if (textView != null && textView.mLayout != null) {
9106                mStatus = MARQUEE_STARTING;
9107                mScroll = 0.0f;
9108                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9109                        textView.getCompoundPaddingRight();
9110                final float lineWidth = textView.mLayout.getLineWidth(0);
9111                final float gap = textWidth / 3.0f;
9112                mGhostStart = lineWidth - textWidth + gap;
9113                mMaxScroll = mGhostStart + textWidth;
9114                mGhostOffset = lineWidth + gap;
9115                mFadeStop = lineWidth + textWidth / 6.0f;
9116                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9117
9118                textView.invalidate();
9119                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9120            }
9121        }
9122
9123        float getGhostOffset() {
9124            return mGhostOffset;
9125        }
9126
9127        float getScroll() {
9128            return mScroll;
9129        }
9130
9131        float getMaxFadeScroll() {
9132            return mMaxFadeScroll;
9133        }
9134
9135        boolean shouldDrawLeftFade() {
9136            return mScroll <= mFadeStop;
9137        }
9138
9139        boolean shouldDrawGhost() {
9140            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9141        }
9142
9143        boolean isRunning() {
9144            return mStatus == MARQUEE_RUNNING;
9145        }
9146
9147        boolean isStopped() {
9148            return mStatus == MARQUEE_STOPPED;
9149        }
9150    }
9151
9152    private class ChangeWatcher implements TextWatcher, SpanWatcher {
9153
9154        private CharSequence mBeforeText;
9155
9156        public void beforeTextChanged(CharSequence buffer, int start,
9157                                      int before, int after) {
9158            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9159                    + " before=" + before + " after=" + after + ": " + buffer);
9160
9161            if (AccessibilityManager.getInstance(mContext).isEnabled()
9162                    && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9163                            || shouldSpeakPasswordsForAccessibility())) {
9164                mBeforeText = buffer.toString();
9165            }
9166
9167            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9168        }
9169
9170        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
9171            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9172                    + " before=" + before + " after=" + after + ": " + buffer);
9173            TextView.this.handleTextChanged(buffer, start, before, after);
9174
9175            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9176                    (isFocused() || isSelected() && isShown())) {
9177                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9178                mBeforeText = null;
9179            }
9180        }
9181
9182        public void afterTextChanged(Editable buffer) {
9183            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9184            TextView.this.sendAfterTextChanged(buffer);
9185
9186            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9187                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9188            }
9189        }
9190
9191        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
9192            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9193                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9194            TextView.this.spanChange(buf, what, s, st, e, en);
9195        }
9196
9197        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9198            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9199                    + " what=" + what + ": " + buf);
9200            TextView.this.spanChange(buf, what, -1, s, -1, e);
9201        }
9202
9203        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9204            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9205                    + " what=" + what + ": " + buf);
9206            TextView.this.spanChange(buf, what, s, -1, e, -1);
9207        }
9208    }
9209}
9210