TextView.java revision 4213804541a8b05cd0587b138a2fd9a3b7fd9350
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) getEditor().invalidateTextDisplayList();
6864        }
6865
6866        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6867            mHighlightPathBogus = true;
6868            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6869                ims.mSelectionModeChanged = true;
6870            }
6871
6872            if (Selection.getSelectionStart(buf) >= 0) {
6873                if (ims == null || ims.mBatchEditNesting == 0) {
6874                    invalidateCursor();
6875                } else {
6876                    ims.mCursorChanged = true;
6877                }
6878            }
6879        }
6880
6881        if (what instanceof ParcelableSpan) {
6882            // If this is a span that can be sent to a remote process,
6883            // the current extract editor would be interested in it.
6884            if (ims != null && ims.mExtracting != null) {
6885                if (ims.mBatchEditNesting != 0) {
6886                    if (oldStart >= 0) {
6887                        if (ims.mChangedStart > oldStart) {
6888                            ims.mChangedStart = oldStart;
6889                        }
6890                        if (ims.mChangedStart > oldEnd) {
6891                            ims.mChangedStart = oldEnd;
6892                        }
6893                    }
6894                    if (newStart >= 0) {
6895                        if (ims.mChangedStart > newStart) {
6896                            ims.mChangedStart = newStart;
6897                        }
6898                        if (ims.mChangedStart > newEnd) {
6899                            ims.mChangedStart = newEnd;
6900                        }
6901                    }
6902                } else {
6903                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
6904                            + oldStart + "-" + oldEnd + ","
6905                            + newStart + "-" + newEnd + what);
6906                    ims.mContentChanged = true;
6907                }
6908            }
6909        }
6910
6911        if (mEditor != null && getEditor().mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
6912            getEditor().mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
6913        }
6914    }
6915
6916    /**
6917     * @hide
6918     */
6919    @Override
6920    public void dispatchFinishTemporaryDetach() {
6921        mDispatchTemporaryDetach = true;
6922        super.dispatchFinishTemporaryDetach();
6923        mDispatchTemporaryDetach = false;
6924    }
6925
6926    @Override
6927    public void onStartTemporaryDetach() {
6928        super.onStartTemporaryDetach();
6929        // Only track when onStartTemporaryDetach() is called directly,
6930        // usually because this instance is an editable field in a list
6931        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
6932
6933        // Because of View recycling in ListView, there is no easy way to know when a TextView with
6934        // selection becomes visible again. Until a better solution is found, stop text selection
6935        // mode (if any) as soon as this TextView is recycled.
6936        if (mEditor != null) getEditor().hideControllers();
6937    }
6938
6939    @Override
6940    public void onFinishTemporaryDetach() {
6941        super.onFinishTemporaryDetach();
6942        // Only track when onStartTemporaryDetach() is called directly,
6943        // usually because this instance is an editable field in a list
6944        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
6945    }
6946
6947    @Override
6948    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
6949        if (mTemporaryDetach) {
6950            // If we are temporarily in the detach state, then do nothing.
6951            super.onFocusChanged(focused, direction, previouslyFocusedRect);
6952            return;
6953        }
6954
6955        if (mEditor != null) getEditor().onFocusChanged(focused, direction);
6956
6957        if (focused) {
6958            if (mText instanceof Spannable) {
6959                Spannable sp = (Spannable) mText;
6960                MetaKeyKeyListener.resetMetaState(sp);
6961            }
6962        }
6963
6964        startStopMarquee(focused);
6965
6966        if (mTransformation != null) {
6967            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
6968        }
6969
6970        super.onFocusChanged(focused, direction, previouslyFocusedRect);
6971    }
6972
6973    @Override
6974    public void onWindowFocusChanged(boolean hasWindowFocus) {
6975        super.onWindowFocusChanged(hasWindowFocus);
6976
6977        if (mEditor != null) getEditor().onWindowFocusChanged(hasWindowFocus);
6978
6979        startStopMarquee(hasWindowFocus);
6980    }
6981
6982    @Override
6983    protected void onVisibilityChanged(View changedView, int visibility) {
6984        super.onVisibilityChanged(changedView, visibility);
6985        if (mEditor != null && visibility != VISIBLE) {
6986            getEditor().hideControllers();
6987        }
6988    }
6989
6990    /**
6991     * Use {@link BaseInputConnection#removeComposingSpans
6992     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
6993     * state from this text view.
6994     */
6995    public void clearComposingText() {
6996        if (mText instanceof Spannable) {
6997            BaseInputConnection.removeComposingSpans((Spannable)mText);
6998        }
6999    }
7000
7001    @Override
7002    public void setSelected(boolean selected) {
7003        boolean wasSelected = isSelected();
7004
7005        super.setSelected(selected);
7006
7007        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7008            if (selected) {
7009                startMarquee();
7010            } else {
7011                stopMarquee();
7012            }
7013        }
7014    }
7015
7016    @Override
7017    public boolean onTouchEvent(MotionEvent event) {
7018        final int action = event.getActionMasked();
7019
7020        if (mEditor != null) getEditor().onTouchEvent(event);
7021
7022        final boolean superResult = super.onTouchEvent(event);
7023
7024        /*
7025         * Don't handle the release after a long press, because it will
7026         * move the selection away from whatever the menu action was
7027         * trying to affect.
7028         */
7029        if (mEditor != null && getEditor().mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7030            getEditor().mDiscardNextActionUp = false;
7031            return superResult;
7032        }
7033
7034        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
7035                (mEditor == null || !getEditor().mIgnoreActionUpEvent) && isFocused();
7036
7037         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7038                && mText instanceof Spannable && mLayout != null) {
7039            boolean handled = false;
7040
7041            if (mMovement != null) {
7042                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7043            }
7044
7045            final boolean textIsSelectable = isTextSelectable();
7046            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
7047                // The LinkMovementMethod which should handle taps on links has not been installed
7048                // on non editable text that support text selection.
7049                // We reproduce its behavior here to open links for these.
7050                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7051                        getSelectionEnd(), ClickableSpan.class);
7052
7053                if (links.length > 0) {
7054                    links[0].onClick(this);
7055                    handled = true;
7056                }
7057            }
7058
7059            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
7060                // Show the IME, except when selecting in read-only text.
7061                final InputMethodManager imm = InputMethodManager.peekInstance();
7062                viewClicked(imm);
7063                if (!textIsSelectable) {
7064                    handled |= imm != null && imm.showSoftInput(this, 0);
7065                }
7066
7067                // The above condition ensures that the mEditor is not null
7068                getEditor().onTouchUpEvent(event);
7069
7070                handled = true;
7071            }
7072
7073            if (handled) {
7074                return true;
7075            }
7076        }
7077
7078        return superResult;
7079    }
7080
7081    @Override
7082    public boolean onGenericMotionEvent(MotionEvent event) {
7083        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7084            try {
7085                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7086                    return true;
7087                }
7088            } catch (AbstractMethodError ex) {
7089                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7090                // Ignore its absence in case third party applications implemented the
7091                // interface directly.
7092            }
7093        }
7094        return super.onGenericMotionEvent(event);
7095    }
7096
7097    /**
7098     * @return True iff this TextView contains a text that can be edited, or if this is
7099     * a selectable TextView.
7100     */
7101    boolean isTextEditable() {
7102        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
7103    }
7104
7105    /**
7106     * Returns true, only while processing a touch gesture, if the initial
7107     * touch down event caused focus to move to the text view and as a result
7108     * its selection changed.  Only valid while processing the touch gesture
7109     * of interest, in an editable text view.
7110     */
7111    public boolean didTouchFocusSelect() {
7112        return mEditor != null && getEditor().mTouchFocusSelected;
7113    }
7114
7115    @Override
7116    public void cancelLongPress() {
7117        super.cancelLongPress();
7118        if (mEditor != null) getEditor().mIgnoreActionUpEvent = true;
7119    }
7120
7121    @Override
7122    public boolean onTrackballEvent(MotionEvent event) {
7123        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7124            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7125                return true;
7126            }
7127        }
7128
7129        return super.onTrackballEvent(event);
7130    }
7131
7132    public void setScroller(Scroller s) {
7133        mScroller = s;
7134    }
7135
7136    @Override
7137    protected float getLeftFadingEdgeStrength() {
7138        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7139        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7140                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7141            if (mMarquee != null && !mMarquee.isStopped()) {
7142                final Marquee marquee = mMarquee;
7143                if (marquee.shouldDrawLeftFade()) {
7144                    return marquee.mScroll / getHorizontalFadingEdgeLength();
7145                } else {
7146                    return 0.0f;
7147                }
7148            } else if (getLineCount() == 1) {
7149                final int layoutDirection = getResolvedLayoutDirection();
7150                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7151                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7152                    case Gravity.LEFT:
7153                        return 0.0f;
7154                    case Gravity.RIGHT:
7155                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
7156                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
7157                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7158                    case Gravity.CENTER_HORIZONTAL:
7159                        return 0.0f;
7160                }
7161            }
7162        }
7163        return super.getLeftFadingEdgeStrength();
7164    }
7165
7166    @Override
7167    protected float getRightFadingEdgeStrength() {
7168        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7169        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7170                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7171            if (mMarquee != null && !mMarquee.isStopped()) {
7172                final Marquee marquee = mMarquee;
7173                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
7174            } else if (getLineCount() == 1) {
7175                final int layoutDirection = getResolvedLayoutDirection();
7176                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7177                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7178                    case Gravity.LEFT:
7179                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7180                                getCompoundPaddingRight();
7181                        final float lineWidth = mLayout.getLineWidth(0);
7182                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
7183                    case Gravity.RIGHT:
7184                        return 0.0f;
7185                    case Gravity.CENTER_HORIZONTAL:
7186                    case Gravity.FILL_HORIZONTAL:
7187                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7188                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7189                                getHorizontalFadingEdgeLength();
7190                }
7191            }
7192        }
7193        return super.getRightFadingEdgeStrength();
7194    }
7195
7196    @Override
7197    protected int computeHorizontalScrollRange() {
7198        if (mLayout != null) {
7199            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7200                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7201        }
7202
7203        return super.computeHorizontalScrollRange();
7204    }
7205
7206    @Override
7207    protected int computeVerticalScrollRange() {
7208        if (mLayout != null)
7209            return mLayout.getHeight();
7210
7211        return super.computeVerticalScrollRange();
7212    }
7213
7214    @Override
7215    protected int computeVerticalScrollExtent() {
7216        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7217    }
7218
7219    @Override
7220    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7221        super.findViewsWithText(outViews, searched, flags);
7222        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7223                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7224            String searchedLowerCase = searched.toString().toLowerCase();
7225            String textLowerCase = mText.toString().toLowerCase();
7226            if (textLowerCase.contains(searchedLowerCase)) {
7227                outViews.add(this);
7228            }
7229        }
7230    }
7231
7232    public enum BufferType {
7233        NORMAL, SPANNABLE, EDITABLE,
7234    }
7235
7236    /**
7237     * Returns the TextView_textColor attribute from the
7238     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7239     * from the TextView_textAppearance attribute, if TextView_textColor
7240     * was not set directly.
7241     */
7242    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7243        ColorStateList colors;
7244        colors = attrs.getColorStateList(com.android.internal.R.styleable.
7245                                         TextView_textColor);
7246
7247        if (colors == null) {
7248            int ap = attrs.getResourceId(com.android.internal.R.styleable.
7249                                         TextView_textAppearance, -1);
7250            if (ap != -1) {
7251                TypedArray appearance;
7252                appearance = context.obtainStyledAttributes(ap,
7253                                            com.android.internal.R.styleable.TextAppearance);
7254                colors = appearance.getColorStateList(com.android.internal.R.styleable.
7255                                                  TextAppearance_textColor);
7256                appearance.recycle();
7257            }
7258        }
7259
7260        return colors;
7261    }
7262
7263    /**
7264     * Returns the default color from the TextView_textColor attribute
7265     * from the AttributeSet, if set, or the default color from the
7266     * TextAppearance_textColor from the TextView_textAppearance attribute,
7267     * if TextView_textColor was not set directly.
7268     */
7269    public static int getTextColor(Context context,
7270                                   TypedArray attrs,
7271                                   int def) {
7272        ColorStateList colors = getTextColors(context, attrs);
7273
7274        if (colors == null) {
7275            return def;
7276        } else {
7277            return colors.getDefaultColor();
7278        }
7279    }
7280
7281    @Override
7282    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
7283        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7284        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7285            switch (keyCode) {
7286            case KeyEvent.KEYCODE_A:
7287                if (canSelectText()) {
7288                    return onTextContextMenuItem(ID_SELECT_ALL);
7289                }
7290                break;
7291            case KeyEvent.KEYCODE_X:
7292                if (canCut()) {
7293                    return onTextContextMenuItem(ID_CUT);
7294                }
7295                break;
7296            case KeyEvent.KEYCODE_C:
7297                if (canCopy()) {
7298                    return onTextContextMenuItem(ID_COPY);
7299                }
7300                break;
7301            case KeyEvent.KEYCODE_V:
7302                if (canPaste()) {
7303                    return onTextContextMenuItem(ID_PASTE);
7304                }
7305                break;
7306            }
7307        }
7308        return super.onKeyShortcut(keyCode, event);
7309    }
7310
7311    /**
7312     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7313     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
7314     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not sufficient.
7315     */
7316    private boolean canSelectText() {
7317        return mText.length() != 0 && mEditor != null && getEditor().hasSelectionController();
7318    }
7319
7320    /**
7321     * Test based on the <i>intrinsic</i> charateristics of the TextView.
7322     * The text must be spannable and the movement method must allow for arbitary selection.
7323     *
7324     * See also {@link #canSelectText()}.
7325     */
7326    boolean textCanBeSelected() {
7327        // prepareCursorController() relies on this method.
7328        // If you change this condition, make sure prepareCursorController is called anywhere
7329        // the value of this condition might be changed.
7330        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
7331        return isTextEditable() || (isTextSelectable() && mText instanceof Spannable && isEnabled());
7332    }
7333
7334    /**
7335     * This is a temporary method. Future versions may support multi-locale text.
7336     *
7337     * @return The locale that should be used for a word iterator and a spell checker
7338     * in this TextView, based on the current spell checker settings,
7339     * the current IME's locale, or the system default locale.
7340     * @hide
7341     */
7342    public Locale getTextServicesLocale() {
7343        Locale locale = Locale.getDefault();
7344        final TextServicesManager textServicesManager = (TextServicesManager)
7345                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
7346        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
7347        if (subtype != null) {
7348            locale = new Locale(subtype.getLocale());
7349        }
7350        return locale;
7351    }
7352
7353    void onLocaleChanged() {
7354        // Will be re-created on demand in getWordIterator with the proper new locale
7355        getEditor().mWordIterator = null;
7356    }
7357
7358    /**
7359     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
7360     * Made available to achieve a consistent behavior.
7361     * @hide
7362     */
7363    public WordIterator getWordIterator() {
7364        if (getEditor() != null) {
7365            return mEditor.getWordIterator();
7366        } else {
7367            return null;
7368        }
7369    }
7370
7371    @Override
7372    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
7373        super.onPopulateAccessibilityEvent(event);
7374
7375        final boolean isPassword = hasPasswordTransformationMethod();
7376        if (!isPassword) {
7377            CharSequence text = getTextForAccessibility();
7378            if (!TextUtils.isEmpty(text)) {
7379                event.getText().add(text);
7380            }
7381        }
7382    }
7383
7384    @Override
7385    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
7386        super.onInitializeAccessibilityEvent(event);
7387
7388        event.setClassName(TextView.class.getName());
7389        final boolean isPassword = hasPasswordTransformationMethod();
7390        event.setPassword(isPassword);
7391
7392        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
7393            event.setFromIndex(Selection.getSelectionStart(mText));
7394            event.setToIndex(Selection.getSelectionEnd(mText));
7395            event.setItemCount(mText.length());
7396        }
7397    }
7398
7399    @Override
7400    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
7401        super.onInitializeAccessibilityNodeInfo(info);
7402
7403        info.setClassName(TextView.class.getName());
7404        final boolean isPassword = hasPasswordTransformationMethod();
7405        info.setPassword(isPassword);
7406
7407        if (!isPassword) {
7408            info.setText(getTextForAccessibility());
7409        }
7410    }
7411
7412    @Override
7413    public void sendAccessibilityEvent(int eventType) {
7414        // Do not send scroll events since first they are not interesting for
7415        // accessibility and second such events a generated too frequently.
7416        // For details see the implementation of bringTextIntoView().
7417        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
7418            return;
7419        }
7420        super.sendAccessibilityEvent(eventType);
7421    }
7422
7423    /**
7424     * Gets the text reported for accessibility purposes. It is the
7425     * text if not empty or the hint.
7426     *
7427     * @return The accessibility text.
7428     */
7429    private CharSequence getTextForAccessibility() {
7430        CharSequence text = getText();
7431        if (TextUtils.isEmpty(text)) {
7432            text = getHint();
7433        }
7434        return text;
7435    }
7436
7437    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7438            int fromIndex, int removedCount, int addedCount) {
7439        AccessibilityEvent event =
7440            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7441        event.setFromIndex(fromIndex);
7442        event.setRemovedCount(removedCount);
7443        event.setAddedCount(addedCount);
7444        event.setBeforeText(beforeText);
7445        sendAccessibilityEventUnchecked(event);
7446    }
7447
7448    /**
7449     * Returns whether this text view is a current input method target.  The
7450     * default implementation just checks with {@link InputMethodManager}.
7451     */
7452    public boolean isInputMethodTarget() {
7453        InputMethodManager imm = InputMethodManager.peekInstance();
7454        return imm != null && imm.isActive(this);
7455    }
7456
7457    static final int ID_SELECT_ALL = android.R.id.selectAll;
7458    static final int ID_CUT = android.R.id.cut;
7459    static final int ID_COPY = android.R.id.copy;
7460    static final int ID_PASTE = android.R.id.paste;
7461
7462    /**
7463     * Called when a context menu option for the text view is selected.  Currently
7464     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
7465     * {@link android.R.id#copy} or {@link android.R.id#paste}.
7466     *
7467     * @return true if the context menu item action was performed.
7468     */
7469    public boolean onTextContextMenuItem(int id) {
7470        int min = 0;
7471        int max = mText.length();
7472
7473        if (isFocused()) {
7474            final int selStart = getSelectionStart();
7475            final int selEnd = getSelectionEnd();
7476
7477            min = Math.max(0, Math.min(selStart, selEnd));
7478            max = Math.max(0, Math.max(selStart, selEnd));
7479        }
7480
7481        switch (id) {
7482            case ID_SELECT_ALL:
7483                // This does not enter text selection mode. Text is highlighted, so that it can be
7484                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
7485                selectAllText();
7486                return true;
7487
7488            case ID_PASTE:
7489                paste(min, max);
7490                return true;
7491
7492            case ID_CUT:
7493                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
7494                deleteText_internal(min, max);
7495                stopSelectionActionMode();
7496                return true;
7497
7498            case ID_COPY:
7499                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
7500                stopSelectionActionMode();
7501                return true;
7502        }
7503        return false;
7504    }
7505
7506    CharSequence getTransformedText(int start, int end) {
7507        return removeSuggestionSpans(mTransformed.subSequence(start, end));
7508    }
7509
7510    @Override
7511    public boolean performLongClick() {
7512        boolean handled = false;
7513
7514        if (super.performLongClick()) {
7515            handled = true;
7516        }
7517
7518        if (mEditor != null) {
7519            handled |= getEditor().performLongClick(handled);
7520        }
7521
7522        if (handled) {
7523            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
7524            if (mEditor != null) getEditor().mDiscardNextActionUp = true;
7525        }
7526
7527        return handled;
7528    }
7529
7530    @Override
7531    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
7532        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
7533        if (mEditor != null) {
7534            getEditor().onScrollChanged();
7535        }
7536    }
7537
7538    /**
7539     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
7540     * by the IME or by the spell checker as the user types. This is done by adding
7541     * {@link SuggestionSpan}s to the text.
7542     *
7543     * When suggestions are enabled (default), this list of suggestions will be displayed when the
7544     * user asks for them on these parts of the text. This value depends on the inputType of this
7545     * TextView.
7546     *
7547     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
7548     *
7549     * In addition, the type variation must be one of
7550     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
7551     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
7552     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
7553     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
7554     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
7555     *
7556     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
7557     *
7558     * @return true if the suggestions popup window is enabled, based on the inputType.
7559     */
7560    public boolean isSuggestionsEnabled() {
7561        if (mEditor == null) return false;
7562        if ((getEditor().mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
7563        if ((getEditor().mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
7564
7565        final int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
7566        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
7567                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
7568                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
7569                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
7570                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
7571    }
7572
7573    /**
7574     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
7575     * selection is initiated in this View.
7576     *
7577     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
7578     * Paste actions, depending on what this View supports.
7579     *
7580     * A custom implementation can add new entries in the default menu in its
7581     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
7582     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
7583     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
7584     * or {@link android.R.id#paste} ids as parameters.
7585     *
7586     * Returning false from
7587     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
7588     * the action mode from being started.
7589     *
7590     * Action click events should be handled by the custom implementation of
7591     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
7592     *
7593     * Note that text selection mode is not started when a TextView receives focus and the
7594     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
7595     * that case, to allow for quick replacement.
7596     */
7597    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
7598        createEditorIfNeeded("custom selection action mode set");
7599        getEditor().mCustomSelectionActionModeCallback = actionModeCallback;
7600    }
7601
7602    /**
7603     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
7604     *
7605     * @return The current custom selection callback.
7606     */
7607    public ActionMode.Callback getCustomSelectionActionModeCallback() {
7608        return mEditor == null ? null : getEditor().mCustomSelectionActionModeCallback;
7609    }
7610
7611    /**
7612     * @hide
7613     */
7614    protected void stopSelectionActionMode() {
7615        getEditor().stopSelectionActionMode();
7616    }
7617
7618    boolean canCut() {
7619        if (hasPasswordTransformationMethod()) {
7620            return false;
7621        }
7622
7623        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && getEditor().mKeyListener != null) {
7624            return true;
7625        }
7626
7627        return false;
7628    }
7629
7630    boolean canCopy() {
7631        if (hasPasswordTransformationMethod()) {
7632            return false;
7633        }
7634
7635        if (mText.length() > 0 && hasSelection()) {
7636            return true;
7637        }
7638
7639        return false;
7640    }
7641
7642    boolean canPaste() {
7643        return (mText instanceof Editable &&
7644                mEditor != null && getEditor().mKeyListener != null &&
7645                getSelectionStart() >= 0 &&
7646                getSelectionEnd() >= 0 &&
7647                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
7648                hasPrimaryClip());
7649    }
7650
7651    boolean selectAllText() {
7652        final int length = mText.length();
7653        Selection.setSelection((Spannable) mText, 0, length);
7654        return length > 0;
7655    }
7656
7657    /**
7658     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
7659     * by [min, max] when replacing this region by paste.
7660     * Note that if there were two spaces (or more) at that position before, they are kept. We just
7661     * make sure we do not add an extra one from the paste content.
7662     */
7663    long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
7664        if (paste.length() > 0) {
7665            if (min > 0) {
7666                final char charBefore = mTransformed.charAt(min - 1);
7667                final char charAfter = paste.charAt(0);
7668
7669                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
7670                    // Two spaces at beginning of paste: remove one
7671                    final int originalLength = mText.length();
7672                    deleteText_internal(min - 1, min);
7673                    // Due to filters, there is no guarantee that exactly one character was
7674                    // removed: count instead.
7675                    final int delta = mText.length() - originalLength;
7676                    min += delta;
7677                    max += delta;
7678                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
7679                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
7680                    // No space at beginning of paste: add one
7681                    final int originalLength = mText.length();
7682                    replaceText_internal(min, min, " ");
7683                    // Taking possible filters into account as above.
7684                    final int delta = mText.length() - originalLength;
7685                    min += delta;
7686                    max += delta;
7687                }
7688            }
7689
7690            if (max < mText.length()) {
7691                final char charBefore = paste.charAt(paste.length() - 1);
7692                final char charAfter = mTransformed.charAt(max);
7693
7694                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
7695                    // Two spaces at end of paste: remove one
7696                    deleteText_internal(max, max + 1);
7697                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
7698                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
7699                    // No space at end of paste: add one
7700                    replaceText_internal(max, max, " ");
7701                }
7702            }
7703        }
7704
7705        return TextUtils.packRangeInLong(min, max);
7706    }
7707
7708    /**
7709     * Paste clipboard content between min and max positions.
7710     */
7711    private void paste(int min, int max) {
7712        ClipboardManager clipboard =
7713            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
7714        ClipData clip = clipboard.getPrimaryClip();
7715        if (clip != null) {
7716            boolean didFirst = false;
7717            for (int i=0; i<clip.getItemCount(); i++) {
7718                CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
7719                if (paste != null) {
7720                    if (!didFirst) {
7721                        long minMax = prepareSpacesAroundPaste(min, max, paste);
7722                        min = TextUtils.unpackRangeStartFromLong(minMax);
7723                        max = TextUtils.unpackRangeEndFromLong(minMax);
7724                        Selection.setSelection((Spannable) mText, max);
7725                        ((Editable) mText).replace(min, max, paste);
7726                        didFirst = true;
7727                    } else {
7728                        ((Editable) mText).insert(getSelectionEnd(), "\n");
7729                        ((Editable) mText).insert(getSelectionEnd(), paste);
7730                    }
7731                }
7732            }
7733            stopSelectionActionMode();
7734            LAST_CUT_OR_COPY_TIME = 0;
7735        }
7736    }
7737
7738    private void setPrimaryClip(ClipData clip) {
7739        ClipboardManager clipboard = (ClipboardManager) getContext().
7740                getSystemService(Context.CLIPBOARD_SERVICE);
7741        clipboard.setPrimaryClip(clip);
7742        LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
7743    }
7744
7745    /**
7746     * Get the character offset closest to the specified absolute position. A typical use case is to
7747     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
7748     *
7749     * @param x The horizontal absolute position of a point on screen
7750     * @param y The vertical absolute position of a point on screen
7751     * @return the character offset for the character whose position is closest to the specified
7752     *  position. Returns -1 if there is no layout.
7753     */
7754    public int getOffsetForPosition(float x, float y) {
7755        if (getLayout() == null) return -1;
7756        final int line = getLineAtCoordinate(y);
7757        final int offset = getOffsetAtCoordinate(line, x);
7758        return offset;
7759    }
7760
7761    float convertToLocalHorizontalCoordinate(float x) {
7762        x -= getTotalPaddingLeft();
7763        // Clamp the position to inside of the view.
7764        x = Math.max(0.0f, x);
7765        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
7766        x += getScrollX();
7767        return x;
7768    }
7769
7770    int getLineAtCoordinate(float y) {
7771        y -= getTotalPaddingTop();
7772        // Clamp the position to inside of the view.
7773        y = Math.max(0.0f, y);
7774        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
7775        y += getScrollY();
7776        return getLayout().getLineForVertical((int) y);
7777    }
7778
7779    private int getOffsetAtCoordinate(int line, float x) {
7780        x = convertToLocalHorizontalCoordinate(x);
7781        return getLayout().getOffsetForHorizontal(line, x);
7782    }
7783
7784    @Override
7785    public boolean onDragEvent(DragEvent event) {
7786        switch (event.getAction()) {
7787            case DragEvent.ACTION_DRAG_STARTED:
7788                return mEditor != null && getEditor().hasInsertionController();
7789
7790            case DragEvent.ACTION_DRAG_ENTERED:
7791                TextView.this.requestFocus();
7792                return true;
7793
7794            case DragEvent.ACTION_DRAG_LOCATION:
7795                final int offset = getOffsetForPosition(event.getX(), event.getY());
7796                Selection.setSelection((Spannable)mText, offset);
7797                return true;
7798
7799            case DragEvent.ACTION_DROP:
7800                if (mEditor != null) getEditor().onDrop(event);
7801                return true;
7802
7803            case DragEvent.ACTION_DRAG_ENDED:
7804            case DragEvent.ACTION_DRAG_EXITED:
7805            default:
7806                return true;
7807        }
7808    }
7809
7810    boolean isInBatchEditMode() {
7811        if (mEditor == null) return false;
7812        final Editor.InputMethodState ims = getEditor().mInputMethodState;
7813        if (ims != null) {
7814            return ims.mBatchEditNesting > 0;
7815        }
7816        return getEditor().mInBatchEditControllers;
7817    }
7818
7819    @Override
7820    public void onResolvedTextDirectionChanged() {
7821        if (hasPasswordTransformationMethod()) {
7822            // TODO: take care of the content direction to show the password text and dots justified
7823            // to the left or to the right
7824            mTextDir = TextDirectionHeuristics.LOCALE;
7825            return;
7826        }
7827
7828        // Always need to resolve layout direction first
7829        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
7830
7831        // Now, we can select the heuristic
7832        int textDir = getResolvedTextDirection();
7833        switch (textDir) {
7834            default:
7835            case TEXT_DIRECTION_FIRST_STRONG:
7836                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
7837                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
7838                break;
7839            case TEXT_DIRECTION_ANY_RTL:
7840                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
7841                break;
7842            case TEXT_DIRECTION_LTR:
7843                mTextDir = TextDirectionHeuristics.LTR;
7844                break;
7845            case TEXT_DIRECTION_RTL:
7846                mTextDir = TextDirectionHeuristics.RTL;
7847                break;
7848            case TEXT_DIRECTION_LOCALE:
7849                mTextDir = TextDirectionHeuristics.LOCALE;
7850                break;
7851        }
7852    }
7853
7854    /**
7855     * Subclasses will need to override this method to implement their own way of resolving
7856     * drawables depending on the layout direction.
7857     *
7858     * A call to the super method will be required from the subclasses implementation.
7859     */
7860    protected void resolveDrawables() {
7861        // No need to resolve twice
7862        if (mResolvedDrawables) {
7863            return;
7864        }
7865        // No drawable to resolve
7866        if (mDrawables == null) {
7867            return;
7868        }
7869        // No relative drawable to resolve
7870        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
7871            mResolvedDrawables = true;
7872            return;
7873        }
7874
7875        Drawables dr = mDrawables;
7876        switch(getResolvedLayoutDirection()) {
7877            case LAYOUT_DIRECTION_RTL:
7878                if (dr.mDrawableStart != null) {
7879                    dr.mDrawableRight = dr.mDrawableStart;
7880
7881                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
7882                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
7883                }
7884                if (dr.mDrawableEnd != null) {
7885                    dr.mDrawableLeft = dr.mDrawableEnd;
7886
7887                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
7888                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
7889                }
7890                break;
7891
7892            case LAYOUT_DIRECTION_LTR:
7893            default:
7894                if (dr.mDrawableStart != null) {
7895                    dr.mDrawableLeft = dr.mDrawableStart;
7896
7897                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
7898                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
7899                }
7900                if (dr.mDrawableEnd != null) {
7901                    dr.mDrawableRight = dr.mDrawableEnd;
7902
7903                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
7904                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
7905                }
7906                break;
7907        }
7908        mResolvedDrawables = true;
7909    }
7910
7911    protected void resetResolvedDrawables() {
7912        mResolvedDrawables = false;
7913    }
7914
7915    /**
7916     * @hide
7917     */
7918    protected void viewClicked(InputMethodManager imm) {
7919        if (imm != null) {
7920            imm.viewClicked(this);
7921        }
7922    }
7923
7924    /**
7925     * Deletes the range of text [start, end[.
7926     * @hide
7927     */
7928    protected void deleteText_internal(int start, int end) {
7929        ((Editable) mText).delete(start, end);
7930    }
7931
7932    /**
7933     * Replaces the range of text [start, end[ by replacement text
7934     * @hide
7935     */
7936    protected void replaceText_internal(int start, int end, CharSequence text) {
7937        ((Editable) mText).replace(start, end, text);
7938    }
7939
7940    /**
7941     * Sets a span on the specified range of text
7942     * @hide
7943     */
7944    protected void setSpan_internal(Object span, int start, int end, int flags) {
7945        ((Editable) mText).setSpan(span, start, end, flags);
7946    }
7947
7948    /**
7949     * Moves the cursor to the specified offset position in text
7950     * @hide
7951     */
7952    protected void setCursorPosition_internal(int start, int end) {
7953        Selection.setSelection(((Editable) mText), start, end);
7954    }
7955
7956    /**
7957     * An Editor should be created as soon as any of the editable-specific fields (grouped
7958     * inside the Editor object) is assigned to a non-default value.
7959     * This method will create the Editor if needed.
7960     *
7961     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
7962     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
7963     * Editor for backward compatibility, as soon as one of these fields is assigned.
7964     *
7965     * Also note that for performance reasons, the mEditor is created when needed, but not
7966     * reset when no more edit-specific fields are needed.
7967     */
7968    private void createEditorIfNeeded(String reason) {
7969        if (mEditor == null) {
7970            if (!(this instanceof EditText)) {
7971                Log.e(LOG_TAG + " EDITOR", "Creating Editor on TextView. " + reason);
7972            }
7973            mEditor = new Editor(this);
7974        } else {
7975            if (!(this instanceof EditText)) {
7976                Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason);
7977            }
7978        }
7979    }
7980
7981    private Editor getEditor() {
7982        if (mEditor == null) {
7983            //createEditorIfNeeded("Problem: mEditor is not initialized!");
7984            Log.e(LOG_TAG, "mEditor not initialized. Please send a bug report to debunne@");
7985        }
7986        return mEditor;
7987    }
7988
7989    /**
7990     * User interface state that is stored by TextView for implementing
7991     * {@link View#onSaveInstanceState}.
7992     */
7993    public static class SavedState extends BaseSavedState {
7994        int selStart;
7995        int selEnd;
7996        CharSequence text;
7997        boolean frozenWithFocus;
7998        CharSequence error;
7999
8000        SavedState(Parcelable superState) {
8001            super(superState);
8002        }
8003
8004        @Override
8005        public void writeToParcel(Parcel out, int flags) {
8006            super.writeToParcel(out, flags);
8007            out.writeInt(selStart);
8008            out.writeInt(selEnd);
8009            out.writeInt(frozenWithFocus ? 1 : 0);
8010            TextUtils.writeToParcel(text, out, flags);
8011
8012            if (error == null) {
8013                out.writeInt(0);
8014            } else {
8015                out.writeInt(1);
8016                TextUtils.writeToParcel(error, out, flags);
8017            }
8018        }
8019
8020        @Override
8021        public String toString() {
8022            String str = "TextView.SavedState{"
8023                    + Integer.toHexString(System.identityHashCode(this))
8024                    + " start=" + selStart + " end=" + selEnd;
8025            if (text != null) {
8026                str += " text=" + text;
8027            }
8028            return str + "}";
8029        }
8030
8031        @SuppressWarnings("hiding")
8032        public static final Parcelable.Creator<SavedState> CREATOR
8033                = new Parcelable.Creator<SavedState>() {
8034            public SavedState createFromParcel(Parcel in) {
8035                return new SavedState(in);
8036            }
8037
8038            public SavedState[] newArray(int size) {
8039                return new SavedState[size];
8040            }
8041        };
8042
8043        private SavedState(Parcel in) {
8044            super(in);
8045            selStart = in.readInt();
8046            selEnd = in.readInt();
8047            frozenWithFocus = (in.readInt() != 0);
8048            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8049
8050            if (in.readInt() != 0) {
8051                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8052            }
8053        }
8054    }
8055
8056    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8057        private char[] mChars;
8058        private int mStart, mLength;
8059
8060        public CharWrapper(char[] chars, int start, int len) {
8061            mChars = chars;
8062            mStart = start;
8063            mLength = len;
8064        }
8065
8066        /* package */ void set(char[] chars, int start, int len) {
8067            mChars = chars;
8068            mStart = start;
8069            mLength = len;
8070        }
8071
8072        public int length() {
8073            return mLength;
8074        }
8075
8076        public char charAt(int off) {
8077            return mChars[off + mStart];
8078        }
8079
8080        @Override
8081        public String toString() {
8082            return new String(mChars, mStart, mLength);
8083        }
8084
8085        public CharSequence subSequence(int start, int end) {
8086            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8087                throw new IndexOutOfBoundsException(start + ", " + end);
8088            }
8089
8090            return new String(mChars, start + mStart, end - start);
8091        }
8092
8093        public void getChars(int start, int end, char[] buf, int off) {
8094            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8095                throw new IndexOutOfBoundsException(start + ", " + end);
8096            }
8097
8098            System.arraycopy(mChars, start + mStart, buf, off, end - start);
8099        }
8100
8101        public void drawText(Canvas c, int start, int end,
8102                             float x, float y, Paint p) {
8103            c.drawText(mChars, start + mStart, end - start, x, y, p);
8104        }
8105
8106        public void drawTextRun(Canvas c, int start, int end,
8107                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
8108            int count = end - start;
8109            int contextCount = contextEnd - contextStart;
8110            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
8111                    contextCount, x, y, flags, p);
8112        }
8113
8114        public float measureText(int start, int end, Paint p) {
8115            return p.measureText(mChars, start + mStart, end - start);
8116        }
8117
8118        public int getTextWidths(int start, int end, float[] widths, Paint p) {
8119            return p.getTextWidths(mChars, start + mStart, end - start, widths);
8120        }
8121
8122        public float getTextRunAdvances(int start, int end, int contextStart,
8123                int contextEnd, int flags, float[] advances, int advancesIndex,
8124                Paint p) {
8125            int count = end - start;
8126            int contextCount = contextEnd - contextStart;
8127            return p.getTextRunAdvances(mChars, start + mStart, count,
8128                    contextStart + mStart, contextCount, flags, advances,
8129                    advancesIndex);
8130        }
8131
8132        public float getTextRunAdvances(int start, int end, int contextStart,
8133                int contextEnd, int flags, float[] advances, int advancesIndex,
8134                Paint p, int reserved) {
8135            int count = end - start;
8136            int contextCount = contextEnd - contextStart;
8137            return p.getTextRunAdvances(mChars, start + mStart, count,
8138                    contextStart + mStart, contextCount, flags, advances,
8139                    advancesIndex, reserved);
8140        }
8141
8142        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
8143                int offset, int cursorOpt, Paint p) {
8144            int contextCount = contextEnd - contextStart;
8145            return p.getTextRunCursor(mChars, contextStart + mStart,
8146                    contextCount, flags, offset + mStart, cursorOpt);
8147        }
8148    }
8149
8150    private static final class Marquee extends Handler {
8151        // TODO: Add an option to configure this
8152        private static final float MARQUEE_DELTA_MAX = 0.07f;
8153        private static final int MARQUEE_DELAY = 1200;
8154        private static final int MARQUEE_RESTART_DELAY = 1200;
8155        private static final int MARQUEE_RESOLUTION = 1000 / 30;
8156        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
8157
8158        private static final byte MARQUEE_STOPPED = 0x0;
8159        private static final byte MARQUEE_STARTING = 0x1;
8160        private static final byte MARQUEE_RUNNING = 0x2;
8161
8162        private static final int MESSAGE_START = 0x1;
8163        private static final int MESSAGE_TICK = 0x2;
8164        private static final int MESSAGE_RESTART = 0x3;
8165
8166        private final WeakReference<TextView> mView;
8167
8168        private byte mStatus = MARQUEE_STOPPED;
8169        private final float mScrollUnit;
8170        private float mMaxScroll;
8171        float mMaxFadeScroll;
8172        private float mGhostStart;
8173        private float mGhostOffset;
8174        private float mFadeStop;
8175        private int mRepeatLimit;
8176
8177        float mScroll;
8178
8179        Marquee(TextView v) {
8180            final float density = v.getContext().getResources().getDisplayMetrics().density;
8181            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
8182            mView = new WeakReference<TextView>(v);
8183        }
8184
8185        @Override
8186        public void handleMessage(Message msg) {
8187            switch (msg.what) {
8188                case MESSAGE_START:
8189                    mStatus = MARQUEE_RUNNING;
8190                    tick();
8191                    break;
8192                case MESSAGE_TICK:
8193                    tick();
8194                    break;
8195                case MESSAGE_RESTART:
8196                    if (mStatus == MARQUEE_RUNNING) {
8197                        if (mRepeatLimit >= 0) {
8198                            mRepeatLimit--;
8199                        }
8200                        start(mRepeatLimit);
8201                    }
8202                    break;
8203            }
8204        }
8205
8206        void tick() {
8207            if (mStatus != MARQUEE_RUNNING) {
8208                return;
8209            }
8210
8211            removeMessages(MESSAGE_TICK);
8212
8213            final TextView textView = mView.get();
8214            if (textView != null && (textView.isFocused() || textView.isSelected())) {
8215                mScroll += mScrollUnit;
8216                if (mScroll > mMaxScroll) {
8217                    mScroll = mMaxScroll;
8218                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
8219                } else {
8220                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
8221                }
8222                textView.invalidate();
8223            }
8224        }
8225
8226        void stop() {
8227            mStatus = MARQUEE_STOPPED;
8228            removeMessages(MESSAGE_START);
8229            removeMessages(MESSAGE_RESTART);
8230            removeMessages(MESSAGE_TICK);
8231            resetScroll();
8232        }
8233
8234        private void resetScroll() {
8235            mScroll = 0.0f;
8236            final TextView textView = mView.get();
8237            if (textView != null) textView.invalidate();
8238        }
8239
8240        void start(int repeatLimit) {
8241            if (repeatLimit == 0) {
8242                stop();
8243                return;
8244            }
8245            mRepeatLimit = repeatLimit;
8246            final TextView textView = mView.get();
8247            if (textView != null && textView.mLayout != null) {
8248                mStatus = MARQUEE_STARTING;
8249                mScroll = 0.0f;
8250                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
8251                        textView.getCompoundPaddingRight();
8252                final float lineWidth = textView.mLayout.getLineWidth(0);
8253                final float gap = textWidth / 3.0f;
8254                mGhostStart = lineWidth - textWidth + gap;
8255                mMaxScroll = mGhostStart + textWidth;
8256                mGhostOffset = lineWidth + gap;
8257                mFadeStop = lineWidth + textWidth / 6.0f;
8258                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
8259
8260                textView.invalidate();
8261                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
8262            }
8263        }
8264
8265        float getGhostOffset() {
8266            return mGhostOffset;
8267        }
8268
8269        boolean shouldDrawLeftFade() {
8270            return mScroll <= mFadeStop;
8271        }
8272
8273        boolean shouldDrawGhost() {
8274            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
8275        }
8276
8277        boolean isRunning() {
8278            return mStatus == MARQUEE_RUNNING;
8279        }
8280
8281        boolean isStopped() {
8282            return mStatus == MARQUEE_STOPPED;
8283        }
8284    }
8285
8286    private class ChangeWatcher implements TextWatcher, SpanWatcher {
8287
8288        private CharSequence mBeforeText;
8289
8290        public void beforeTextChanged(CharSequence buffer, int start,
8291                                      int before, int after) {
8292            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
8293                    + " before=" + before + " after=" + after + ": " + buffer);
8294
8295            if (AccessibilityManager.getInstance(mContext).isEnabled()
8296                    && !isPasswordInputType(getInputType())
8297                    && !hasPasswordTransformationMethod()) {
8298                mBeforeText = buffer.toString();
8299            }
8300
8301            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
8302        }
8303
8304        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
8305            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
8306                    + " before=" + before + " after=" + after + ": " + buffer);
8307            TextView.this.handleTextChanged(buffer, start, before, after);
8308
8309            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
8310                    (isFocused() || isSelected() && isShown())) {
8311                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
8312                mBeforeText = null;
8313            }
8314        }
8315
8316        public void afterTextChanged(Editable buffer) {
8317            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
8318            TextView.this.sendAfterTextChanged(buffer);
8319
8320            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
8321                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
8322            }
8323        }
8324
8325        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
8326            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
8327                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
8328            TextView.this.spanChange(buf, what, s, st, e, en);
8329        }
8330
8331        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
8332            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
8333                    + " what=" + what + ": " + buf);
8334            TextView.this.spanChange(buf, what, -1, s, -1, e);
8335        }
8336
8337        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
8338            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
8339                    + " what=" + what + ": " + buf);
8340            TextView.this.spanChange(buf, what, s, -1, e, -1);
8341        }
8342    }
8343}
8344