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