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