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