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