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