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