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