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