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