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