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