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