TextView.java revision 878a5a6cd7109183928b9b8b533f11347fd0591a
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    @Override
5022    protected int getRightPaddingOffset() {
5023        return -(getCompoundPaddingRight() - mPaddingRight) +
5024                (int) Math.max(0, mShadowDx + mShadowRadius);
5025    }
5026
5027    @Override
5028    protected boolean verifyDrawable(Drawable who) {
5029        final boolean verified = super.verifyDrawable(who);
5030        if (!verified && mDrawables != null) {
5031            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
5032                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
5033                    who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
5034        }
5035        return verified;
5036    }
5037
5038    @Override
5039    public void jumpDrawablesToCurrentState() {
5040        super.jumpDrawablesToCurrentState();
5041        if (mDrawables != null) {
5042            if (mDrawables.mDrawableLeft != null) {
5043                mDrawables.mDrawableLeft.jumpToCurrentState();
5044            }
5045            if (mDrawables.mDrawableTop != null) {
5046                mDrawables.mDrawableTop.jumpToCurrentState();
5047            }
5048            if (mDrawables.mDrawableRight != null) {
5049                mDrawables.mDrawableRight.jumpToCurrentState();
5050            }
5051            if (mDrawables.mDrawableBottom != null) {
5052                mDrawables.mDrawableBottom.jumpToCurrentState();
5053            }
5054            if (mDrawables.mDrawableStart != null) {
5055                mDrawables.mDrawableStart.jumpToCurrentState();
5056            }
5057            if (mDrawables.mDrawableEnd != null) {
5058                mDrawables.mDrawableEnd.jumpToCurrentState();
5059            }
5060        }
5061    }
5062
5063    @Override
5064    public void invalidateDrawable(Drawable drawable) {
5065        boolean handled = false;
5066
5067        if (verifyDrawable(drawable)) {
5068            final Rect dirty = drawable.getBounds();
5069            int scrollX = mScrollX;
5070            int scrollY = mScrollY;
5071
5072            // IMPORTANT: The coordinates below are based on the coordinates computed
5073            // for each compound drawable in onDraw(). Make sure to update each section
5074            // accordingly.
5075            final TextView.Drawables drawables = mDrawables;
5076            if (drawables != null) {
5077                if (drawable == drawables.mDrawableLeft) {
5078                    final int compoundPaddingTop = getCompoundPaddingTop();
5079                    final int compoundPaddingBottom = getCompoundPaddingBottom();
5080                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5081
5082                    scrollX += mPaddingLeft;
5083                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5084                    handled = true;
5085                } else if (drawable == drawables.mDrawableRight) {
5086                    final int compoundPaddingTop = getCompoundPaddingTop();
5087                    final int compoundPaddingBottom = getCompoundPaddingBottom();
5088                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5089
5090                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5091                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5092                    handled = true;
5093                } else if (drawable == drawables.mDrawableTop) {
5094                    final int compoundPaddingLeft = getCompoundPaddingLeft();
5095                    final int compoundPaddingRight = getCompoundPaddingRight();
5096                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5097
5098                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5099                    scrollY += mPaddingTop;
5100                    handled = true;
5101                } else if (drawable == drawables.mDrawableBottom) {
5102                    final int compoundPaddingLeft = getCompoundPaddingLeft();
5103                    final int compoundPaddingRight = getCompoundPaddingRight();
5104                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5105
5106                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5107                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5108                    handled = true;
5109                }
5110            }
5111
5112            if (handled) {
5113                invalidate(dirty.left + scrollX, dirty.top + scrollY,
5114                        dirty.right + scrollX, dirty.bottom + scrollY);
5115            }
5116        }
5117
5118        if (!handled) {
5119            super.invalidateDrawable(drawable);
5120        }
5121    }
5122
5123    @Override
5124    public boolean hasOverlappingRendering() {
5125        // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5126        return ((getBackground() != null && getBackground().getCurrent() != null)
5127                || mText instanceof Spannable || hasSelection()
5128                || isHorizontalFadingEdgeEnabled());
5129    }
5130
5131    /**
5132     *
5133     * Returns the state of the {@code textIsSelectable} flag (See
5134     * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5135     * to allow users to select and copy text in a non-editable TextView, the content of an
5136     * {@link EditText} can always be selected, independently of the value of this flag.
5137     * <p>
5138     *
5139     * @return True if the text displayed in this TextView can be selected by the user.
5140     *
5141     * @attr ref android.R.styleable#TextView_textIsSelectable
5142     */
5143    public boolean isTextSelectable() {
5144        return mEditor == null ? false : mEditor.mTextIsSelectable;
5145    }
5146
5147    /**
5148     * Sets whether the content of this view is selectable by the user. The default is
5149     * {@code false}, meaning that the content is not selectable.
5150     * <p>
5151     * When you use a TextView to display a useful piece of information to the user (such as a
5152     * contact's address), make it selectable, so that the user can select and copy its
5153     * content. You can also use set the XML attribute
5154     * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5155     * <p>
5156     * When you call this method to set the value of {@code textIsSelectable}, it sets
5157     * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5158     * and {@code longClickable} to the same value. These flags correspond to the attributes
5159     * {@link android.R.styleable#View_focusable android:focusable},
5160     * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5161     * {@link android.R.styleable#View_clickable android:clickable}, and
5162     * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5163     * flags to a state you had set previously, call one or more of the following methods:
5164     * {@link #setFocusable(boolean) setFocusable()},
5165     * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5166     * {@link #setClickable(boolean) setClickable()} or
5167     * {@link #setLongClickable(boolean) setLongClickable()}.
5168     *
5169     * @param selectable Whether the content of this TextView should be selectable.
5170     */
5171    public void setTextIsSelectable(boolean selectable) {
5172        if (!selectable && mEditor == null) return; // false is default value with no edit data
5173
5174        createEditorIfNeeded();
5175        if (mEditor.mTextIsSelectable == selectable) return;
5176
5177        mEditor.mTextIsSelectable = selectable;
5178        setFocusableInTouchMode(selectable);
5179        setFocusable(selectable);
5180        setClickable(selectable);
5181        setLongClickable(selectable);
5182
5183        // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5184
5185        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5186        setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5187
5188        // Called by setText above, but safer in case of future code changes
5189        mEditor.prepareCursorControllers();
5190    }
5191
5192    @Override
5193    protected int[] onCreateDrawableState(int extraSpace) {
5194        final int[] drawableState;
5195
5196        if (mSingleLine) {
5197            drawableState = super.onCreateDrawableState(extraSpace);
5198        } else {
5199            drawableState = super.onCreateDrawableState(extraSpace + 1);
5200            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5201        }
5202
5203        if (isTextSelectable()) {
5204            // Disable pressed state, which was introduced when TextView was made clickable.
5205            // Prevents text color change.
5206            // setClickable(false) would have a similar effect, but it also disables focus changes
5207            // and long press actions, which are both needed by text selection.
5208            final int length = drawableState.length;
5209            for (int i = 0; i < length; i++) {
5210                if (drawableState[i] == R.attr.state_pressed) {
5211                    final int[] nonPressedState = new int[length - 1];
5212                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5213                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5214                    return nonPressedState;
5215                }
5216            }
5217        }
5218
5219        return drawableState;
5220    }
5221
5222    private Path getUpdatedHighlightPath() {
5223        Path highlight = null;
5224        Paint highlightPaint = mHighlightPaint;
5225
5226        final int selStart = getSelectionStart();
5227        final int selEnd = getSelectionEnd();
5228        if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5229            if (selStart == selEnd) {
5230                if (mEditor != null && mEditor.isCursorVisible() &&
5231                        (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5232                        (2 * Editor.BLINK) < Editor.BLINK) {
5233                    if (mHighlightPathBogus) {
5234                        if (mHighlightPath == null) mHighlightPath = new Path();
5235                        mHighlightPath.reset();
5236                        mLayout.getCursorPath(selStart, mHighlightPath, mText);
5237                        mEditor.updateCursorsPositions();
5238                        mHighlightPathBogus = false;
5239                    }
5240
5241                    // XXX should pass to skin instead of drawing directly
5242                    highlightPaint.setColor(mCurTextColor);
5243                    highlightPaint.setStyle(Paint.Style.STROKE);
5244                    highlight = mHighlightPath;
5245                }
5246            } else {
5247                if (mHighlightPathBogus) {
5248                    if (mHighlightPath == null) mHighlightPath = new Path();
5249                    mHighlightPath.reset();
5250                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5251                    mHighlightPathBogus = false;
5252                }
5253
5254                // XXX should pass to skin instead of drawing directly
5255                highlightPaint.setColor(mHighlightColor);
5256                highlightPaint.setStyle(Paint.Style.FILL);
5257
5258                highlight = mHighlightPath;
5259            }
5260        }
5261        return highlight;
5262    }
5263
5264    /**
5265     * @hide
5266     */
5267    public int getHorizontalOffsetForDrawables() {
5268        return 0;
5269    }
5270
5271    @Override
5272    protected void onDraw(Canvas canvas) {
5273        restartMarqueeIfNeeded();
5274
5275        // Draw the background for this view
5276        super.onDraw(canvas);
5277
5278        final int compoundPaddingLeft = getCompoundPaddingLeft();
5279        final int compoundPaddingTop = getCompoundPaddingTop();
5280        final int compoundPaddingRight = getCompoundPaddingRight();
5281        final int compoundPaddingBottom = getCompoundPaddingBottom();
5282        final int scrollX = mScrollX;
5283        final int scrollY = mScrollY;
5284        final int right = mRight;
5285        final int left = mLeft;
5286        final int bottom = mBottom;
5287        final int top = mTop;
5288        final boolean isLayoutRtl = isLayoutRtl();
5289        final int offset = getHorizontalOffsetForDrawables();
5290        final int leftOffset = isLayoutRtl ? 0 : offset;
5291        final int rightOffset = isLayoutRtl ? offset : 0 ;
5292
5293        final Drawables dr = mDrawables;
5294        if (dr != null) {
5295            /*
5296             * Compound, not extended, because the icon is not clipped
5297             * if the text height is smaller.
5298             */
5299
5300            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5301            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5302
5303            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5304            // Make sure to update invalidateDrawable() when changing this code.
5305            if (dr.mDrawableLeft != null) {
5306                canvas.save();
5307                canvas.translate(scrollX + mPaddingLeft + leftOffset,
5308                                 scrollY + compoundPaddingTop +
5309                                 (vspace - dr.mDrawableHeightLeft) / 2);
5310                dr.mDrawableLeft.draw(canvas);
5311                canvas.restore();
5312            }
5313
5314            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5315            // Make sure to update invalidateDrawable() when changing this code.
5316            if (dr.mDrawableRight != null) {
5317                canvas.save();
5318                canvas.translate(scrollX + right - left - mPaddingRight
5319                        - dr.mDrawableSizeRight - rightOffset,
5320                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5321                dr.mDrawableRight.draw(canvas);
5322                canvas.restore();
5323            }
5324
5325            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5326            // Make sure to update invalidateDrawable() when changing this code.
5327            if (dr.mDrawableTop != null) {
5328                canvas.save();
5329                canvas.translate(scrollX + compoundPaddingLeft +
5330                        (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5331                dr.mDrawableTop.draw(canvas);
5332                canvas.restore();
5333            }
5334
5335            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5336            // Make sure to update invalidateDrawable() when changing this code.
5337            if (dr.mDrawableBottom != null) {
5338                canvas.save();
5339                canvas.translate(scrollX + compoundPaddingLeft +
5340                        (hspace - dr.mDrawableWidthBottom) / 2,
5341                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5342                dr.mDrawableBottom.draw(canvas);
5343                canvas.restore();
5344            }
5345        }
5346
5347        int color = mCurTextColor;
5348
5349        if (mLayout == null) {
5350            assumeLayout();
5351        }
5352
5353        Layout layout = mLayout;
5354
5355        if (mHint != null && mText.length() == 0) {
5356            if (mHintTextColor != null) {
5357                color = mCurHintTextColor;
5358            }
5359
5360            layout = mHintLayout;
5361        }
5362
5363        mTextPaint.setColor(color);
5364        mTextPaint.drawableState = getDrawableState();
5365
5366        canvas.save();
5367        /*  Would be faster if we didn't have to do this. Can we chop the
5368            (displayable) text so that we don't need to do this ever?
5369        */
5370
5371        int extendedPaddingTop = getExtendedPaddingTop();
5372        int extendedPaddingBottom = getExtendedPaddingBottom();
5373
5374        final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5375        final int maxScrollY = mLayout.getHeight() - vspace;
5376
5377        // Add sufficient space for cursor and tone marks
5378        int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5379        int fudgedPaddingRight = Math.max(0, compoundPaddingRight - (cursorWidth - 1));
5380
5381        float clipLeft = compoundPaddingLeft + scrollX;
5382        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5383        float clipRight = right - left - fudgedPaddingRight + scrollX;
5384        float clipBottom = bottom - top + scrollY -
5385                ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5386
5387        if (mShadowRadius != 0) {
5388            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5389            clipRight += Math.max(0, mShadowDx + mShadowRadius);
5390
5391            clipTop += Math.min(0, mShadowDy - mShadowRadius);
5392            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5393        }
5394
5395        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5396
5397        int voffsetText = 0;
5398        int voffsetCursor = 0;
5399
5400        // translate in by our padding
5401        /* shortcircuit calling getVerticaOffset() */
5402        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5403            voffsetText = getVerticalOffset(false);
5404            voffsetCursor = getVerticalOffset(true);
5405        }
5406        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5407
5408        final int layoutDirection = getLayoutDirection();
5409        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5410        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5411                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5412            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5413                    (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5414                final int width = mRight - mLeft;
5415                final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5416                final float dx = mLayout.getLineRight(0) - (width - padding);
5417                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5418            }
5419
5420            if (mMarquee != null && mMarquee.isRunning()) {
5421                final float dx = -mMarquee.getScroll();
5422                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5423            }
5424        }
5425
5426        final int cursorOffsetVertical = voffsetCursor - voffsetText;
5427
5428        Path highlight = getUpdatedHighlightPath();
5429        if (mEditor != null) {
5430            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5431        } else {
5432            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5433        }
5434
5435        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5436            final float dx = mMarquee.getGhostOffset();
5437            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5438            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5439        }
5440
5441        canvas.restore();
5442    }
5443
5444    @Override
5445    public void getFocusedRect(Rect r) {
5446        if (mLayout == null) {
5447            super.getFocusedRect(r);
5448            return;
5449        }
5450
5451        int selEnd = getSelectionEnd();
5452        if (selEnd < 0) {
5453            super.getFocusedRect(r);
5454            return;
5455        }
5456
5457        int selStart = getSelectionStart();
5458        if (selStart < 0 || selStart >= selEnd) {
5459            int line = mLayout.getLineForOffset(selEnd);
5460            r.top = mLayout.getLineTop(line);
5461            r.bottom = mLayout.getLineBottom(line);
5462            r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5463            r.right = r.left + 4;
5464        } else {
5465            int lineStart = mLayout.getLineForOffset(selStart);
5466            int lineEnd = mLayout.getLineForOffset(selEnd);
5467            r.top = mLayout.getLineTop(lineStart);
5468            r.bottom = mLayout.getLineBottom(lineEnd);
5469            if (lineStart == lineEnd) {
5470                r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5471                r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5472            } else {
5473                // Selection extends across multiple lines -- make the focused
5474                // rect cover the entire width.
5475                if (mHighlightPathBogus) {
5476                    if (mHighlightPath == null) mHighlightPath = new Path();
5477                    mHighlightPath.reset();
5478                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5479                    mHighlightPathBogus = false;
5480                }
5481                synchronized (TEMP_RECTF) {
5482                    mHighlightPath.computeBounds(TEMP_RECTF, true);
5483                    r.left = (int)TEMP_RECTF.left-1;
5484                    r.right = (int)TEMP_RECTF.right+1;
5485                }
5486            }
5487        }
5488
5489        // Adjust for padding and gravity.
5490        int paddingLeft = getCompoundPaddingLeft();
5491        int paddingTop = getExtendedPaddingTop();
5492        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5493            paddingTop += getVerticalOffset(false);
5494        }
5495        r.offset(paddingLeft, paddingTop);
5496        int paddingBottom = getExtendedPaddingBottom();
5497        r.bottom += paddingBottom;
5498    }
5499
5500    /**
5501     * Return the number of lines of text, or 0 if the internal Layout has not
5502     * been built.
5503     */
5504    public int getLineCount() {
5505        return mLayout != null ? mLayout.getLineCount() : 0;
5506    }
5507
5508    /**
5509     * Return the baseline for the specified line (0...getLineCount() - 1)
5510     * If bounds is not null, return the top, left, right, bottom extents
5511     * of the specified line in it. If the internal Layout has not been built,
5512     * return 0 and set bounds to (0, 0, 0, 0)
5513     * @param line which line to examine (0..getLineCount() - 1)
5514     * @param bounds Optional. If not null, it returns the extent of the line
5515     * @return the Y-coordinate of the baseline
5516     */
5517    public int getLineBounds(int line, Rect bounds) {
5518        if (mLayout == null) {
5519            if (bounds != null) {
5520                bounds.set(0, 0, 0, 0);
5521            }
5522            return 0;
5523        }
5524        else {
5525            int baseline = mLayout.getLineBounds(line, bounds);
5526
5527            int voffset = getExtendedPaddingTop();
5528            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5529                voffset += getVerticalOffset(true);
5530            }
5531            if (bounds != null) {
5532                bounds.offset(getCompoundPaddingLeft(), voffset);
5533            }
5534            return baseline + voffset;
5535        }
5536    }
5537
5538    @Override
5539    public int getBaseline() {
5540        if (mLayout == null) {
5541            return super.getBaseline();
5542        }
5543
5544        int voffset = 0;
5545        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5546            voffset = getVerticalOffset(true);
5547        }
5548
5549        if (isLayoutModeOptical(mParent)) {
5550            voffset -= getOpticalInsets().top;
5551        }
5552
5553        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5554    }
5555
5556    /**
5557     * @hide
5558     */
5559    @Override
5560    protected int getFadeTop(boolean offsetRequired) {
5561        if (mLayout == null) return 0;
5562
5563        int voffset = 0;
5564        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5565            voffset = getVerticalOffset(true);
5566        }
5567
5568        if (offsetRequired) voffset += getTopPaddingOffset();
5569
5570        return getExtendedPaddingTop() + voffset;
5571    }
5572
5573    /**
5574     * @hide
5575     */
5576    @Override
5577    protected int getFadeHeight(boolean offsetRequired) {
5578        return mLayout != null ? mLayout.getHeight() : 0;
5579    }
5580
5581    @Override
5582    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5583        if (keyCode == KeyEvent.KEYCODE_BACK) {
5584            boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
5585
5586            if (isInSelectionMode) {
5587                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5588                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5589                    if (state != null) {
5590                        state.startTracking(event, this);
5591                    }
5592                    return true;
5593                } else if (event.getAction() == KeyEvent.ACTION_UP) {
5594                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5595                    if (state != null) {
5596                        state.handleUpEvent(event);
5597                    }
5598                    if (event.isTracking() && !event.isCanceled()) {
5599                        stopSelectionActionMode();
5600                        return true;
5601                    }
5602                }
5603            }
5604        }
5605        return super.onKeyPreIme(keyCode, event);
5606    }
5607
5608    @Override
5609    public boolean onKeyDown(int keyCode, KeyEvent event) {
5610        int which = doKeyDown(keyCode, event, null);
5611        if (which == 0) {
5612            return super.onKeyDown(keyCode, event);
5613        }
5614
5615        return true;
5616    }
5617
5618    @Override
5619    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5620        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5621
5622        int which = doKeyDown(keyCode, down, event);
5623        if (which == 0) {
5624            // Go through default dispatching.
5625            return super.onKeyMultiple(keyCode, repeatCount, event);
5626        }
5627        if (which == -1) {
5628            // Consumed the whole thing.
5629            return true;
5630        }
5631
5632        repeatCount--;
5633
5634        // We are going to dispatch the remaining events to either the input
5635        // or movement method.  To do this, we will just send a repeated stream
5636        // of down and up events until we have done the complete repeatCount.
5637        // It would be nice if those interfaces had an onKeyMultiple() method,
5638        // but adding that is a more complicated change.
5639        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5640        if (which == 1) {
5641            // mEditor and mEditor.mInput are not null from doKeyDown
5642            mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5643            while (--repeatCount > 0) {
5644                mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5645                mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5646            }
5647            hideErrorIfUnchanged();
5648
5649        } else if (which == 2) {
5650            // mMovement is not null from doKeyDown
5651            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5652            while (--repeatCount > 0) {
5653                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5654                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5655            }
5656        }
5657
5658        return true;
5659    }
5660
5661    /**
5662     * Returns true if pressing ENTER in this field advances focus instead
5663     * of inserting the character.  This is true mostly in single-line fields,
5664     * but also in mail addresses and subjects which will display on multiple
5665     * lines but where it doesn't make sense to insert newlines.
5666     */
5667    private boolean shouldAdvanceFocusOnEnter() {
5668        if (getKeyListener() == null) {
5669            return false;
5670        }
5671
5672        if (mSingleLine) {
5673            return true;
5674        }
5675
5676        if (mEditor != null &&
5677                (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5678            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5679            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5680                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5681                return true;
5682            }
5683        }
5684
5685        return false;
5686    }
5687
5688    /**
5689     * Returns true if pressing TAB in this field advances focus instead
5690     * of inserting the character.  Insert tabs only in multi-line editors.
5691     */
5692    private boolean shouldAdvanceFocusOnTab() {
5693        if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5694                (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5695            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5696            if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5697                    || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5698                return false;
5699            }
5700        }
5701        return true;
5702    }
5703
5704    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5705        if (!isEnabled()) {
5706            return 0;
5707        }
5708
5709        // If this is the initial keydown, we don't want to prevent a movement away from this view.
5710        // While this shouldn't be necessary because any time we're preventing default movement we
5711        // should be restricting the focus to remain within this view, thus we'll also receive
5712        // the key up event, occasionally key up events will get dropped and we don't want to
5713        // prevent the user from traversing out of this on the next key down.
5714        if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5715            mPreventDefaultMovement = false;
5716        }
5717
5718        switch (keyCode) {
5719            case KeyEvent.KEYCODE_ENTER:
5720                if (event.hasNoModifiers()) {
5721                    // When mInputContentType is set, we know that we are
5722                    // running in a "modern" cupcake environment, so don't need
5723                    // to worry about the application trying to capture
5724                    // enter key events.
5725                    if (mEditor != null && mEditor.mInputContentType != null) {
5726                        // If there is an action listener, given them a
5727                        // chance to consume the event.
5728                        if (mEditor.mInputContentType.onEditorActionListener != null &&
5729                                mEditor.mInputContentType.onEditorActionListener.onEditorAction(
5730                                this, EditorInfo.IME_NULL, event)) {
5731                            mEditor.mInputContentType.enterDown = true;
5732                            // We are consuming the enter key for them.
5733                            return -1;
5734                        }
5735                    }
5736
5737                    // If our editor should move focus when enter is pressed, or
5738                    // this is a generated event from an IME action button, then
5739                    // don't let it be inserted into the text.
5740                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5741                            || shouldAdvanceFocusOnEnter()) {
5742                        if (hasOnClickListeners()) {
5743                            return 0;
5744                        }
5745                        return -1;
5746                    }
5747                }
5748                break;
5749
5750            case KeyEvent.KEYCODE_DPAD_CENTER:
5751                if (event.hasNoModifiers()) {
5752                    if (shouldAdvanceFocusOnEnter()) {
5753                        return 0;
5754                    }
5755                }
5756                break;
5757
5758            case KeyEvent.KEYCODE_TAB:
5759                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5760                    if (shouldAdvanceFocusOnTab()) {
5761                        return 0;
5762                    }
5763                }
5764                break;
5765
5766                // Has to be done on key down (and not on key up) to correctly be intercepted.
5767            case KeyEvent.KEYCODE_BACK:
5768                if (mEditor != null && mEditor.mSelectionActionMode != null) {
5769                    stopSelectionActionMode();
5770                    return -1;
5771                }
5772                break;
5773        }
5774
5775        if (mEditor != null && mEditor.mKeyListener != null) {
5776            boolean doDown = true;
5777            if (otherEvent != null) {
5778                try {
5779                    beginBatchEdit();
5780                    final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5781                            otherEvent);
5782                    hideErrorIfUnchanged();
5783                    doDown = false;
5784                    if (handled) {
5785                        return -1;
5786                    }
5787                } catch (AbstractMethodError e) {
5788                    // onKeyOther was added after 1.0, so if it isn't
5789                    // implemented we need to try to dispatch as a regular down.
5790                } finally {
5791                    endBatchEdit();
5792                }
5793            }
5794
5795            if (doDown) {
5796                beginBatchEdit();
5797                final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5798                        keyCode, event);
5799                endBatchEdit();
5800                hideErrorIfUnchanged();
5801                if (handled) return 1;
5802            }
5803        }
5804
5805        // bug 650865: sometimes we get a key event before a layout.
5806        // don't try to move around if we don't know the layout.
5807
5808        if (mMovement != null && mLayout != null) {
5809            boolean doDown = true;
5810            if (otherEvent != null) {
5811                try {
5812                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5813                            otherEvent);
5814                    doDown = false;
5815                    if (handled) {
5816                        return -1;
5817                    }
5818                } catch (AbstractMethodError e) {
5819                    // onKeyOther was added after 1.0, so if it isn't
5820                    // implemented we need to try to dispatch as a regular down.
5821                }
5822            }
5823            if (doDown) {
5824                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
5825                    if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5826                        mPreventDefaultMovement = true;
5827                    }
5828                    return 2;
5829                }
5830            }
5831        }
5832
5833        return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
5834    }
5835
5836    /**
5837     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5838     * can be recorded.
5839     * @hide
5840     */
5841    public void resetErrorChangedFlag() {
5842        /*
5843         * Keep track of what the error was before doing the input
5844         * so that if an input filter changed the error, we leave
5845         * that error showing.  Otherwise, we take down whatever
5846         * error was showing when the user types something.
5847         */
5848        if (mEditor != null) mEditor.mErrorWasChanged = false;
5849    }
5850
5851    /**
5852     * @hide
5853     */
5854    public void hideErrorIfUnchanged() {
5855        if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
5856            setError(null, null);
5857        }
5858    }
5859
5860    @Override
5861    public boolean onKeyUp(int keyCode, KeyEvent event) {
5862        if (!isEnabled()) {
5863            return super.onKeyUp(keyCode, event);
5864        }
5865
5866        if (!KeyEvent.isModifierKey(keyCode)) {
5867            mPreventDefaultMovement = false;
5868        }
5869
5870        switch (keyCode) {
5871            case KeyEvent.KEYCODE_DPAD_CENTER:
5872                if (event.hasNoModifiers()) {
5873                    /*
5874                     * If there is a click listener, just call through to
5875                     * super, which will invoke it.
5876                     *
5877                     * If there isn't a click listener, try to show the soft
5878                     * input method.  (It will also
5879                     * call performClick(), but that won't do anything in
5880                     * this case.)
5881                     */
5882                    if (!hasOnClickListeners()) {
5883                        if (mMovement != null && mText instanceof Editable
5884                                && mLayout != null && onCheckIsTextEditor()) {
5885                            InputMethodManager imm = InputMethodManager.peekInstance();
5886                            viewClicked(imm);
5887                            if (imm != null && getShowSoftInputOnFocus()) {
5888                                imm.showSoftInput(this, 0);
5889                            }
5890                        }
5891                    }
5892                }
5893                return super.onKeyUp(keyCode, event);
5894
5895            case KeyEvent.KEYCODE_ENTER:
5896                if (event.hasNoModifiers()) {
5897                    if (mEditor != null && mEditor.mInputContentType != null
5898                            && mEditor.mInputContentType.onEditorActionListener != null
5899                            && mEditor.mInputContentType.enterDown) {
5900                        mEditor.mInputContentType.enterDown = false;
5901                        if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
5902                                this, EditorInfo.IME_NULL, event)) {
5903                            return true;
5904                        }
5905                    }
5906
5907                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5908                            || shouldAdvanceFocusOnEnter()) {
5909                        /*
5910                         * If there is a click listener, just call through to
5911                         * super, which will invoke it.
5912                         *
5913                         * If there isn't a click listener, try to advance focus,
5914                         * but still call through to super, which will reset the
5915                         * pressed state and longpress state.  (It will also
5916                         * call performClick(), but that won't do anything in
5917                         * this case.)
5918                         */
5919                        if (!hasOnClickListeners()) {
5920                            View v = focusSearch(FOCUS_DOWN);
5921
5922                            if (v != null) {
5923                                if (!v.requestFocus(FOCUS_DOWN)) {
5924                                    throw new IllegalStateException(
5925                                            "focus search returned a view " +
5926                                            "that wasn't able to take focus!");
5927                                }
5928
5929                                /*
5930                                 * Return true because we handled the key; super
5931                                 * will return false because there was no click
5932                                 * listener.
5933                                 */
5934                                super.onKeyUp(keyCode, event);
5935                                return true;
5936                            } else if ((event.getFlags()
5937                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5938                                // No target for next focus, but make sure the IME
5939                                // if this came from it.
5940                                InputMethodManager imm = InputMethodManager.peekInstance();
5941                                if (imm != null && imm.isActive(this)) {
5942                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5943                                }
5944                            }
5945                        }
5946                    }
5947                    return super.onKeyUp(keyCode, event);
5948                }
5949                break;
5950        }
5951
5952        if (mEditor != null && mEditor.mKeyListener != null)
5953            if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
5954                return true;
5955
5956        if (mMovement != null && mLayout != null)
5957            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5958                return true;
5959
5960        return super.onKeyUp(keyCode, event);
5961    }
5962
5963    @Override
5964    public boolean onCheckIsTextEditor() {
5965        return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
5966    }
5967
5968    @Override
5969    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5970        if (onCheckIsTextEditor() && isEnabled()) {
5971            mEditor.createInputMethodStateIfNeeded();
5972            outAttrs.inputType = getInputType();
5973            if (mEditor.mInputContentType != null) {
5974                outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5975                outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5976                outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5977                outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5978                outAttrs.extras = mEditor.mInputContentType.extras;
5979            } else {
5980                outAttrs.imeOptions = EditorInfo.IME_NULL;
5981            }
5982            if (focusSearch(FOCUS_DOWN) != null) {
5983                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5984            }
5985            if (focusSearch(FOCUS_UP) != null) {
5986                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5987            }
5988            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5989                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
5990                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
5991                    // An action has not been set, but the enter key will move to
5992                    // the next focus, so set the action to that.
5993                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
5994                } else {
5995                    // An action has not been set, and there is no focus to move
5996                    // to, so let's just supply a "done" action.
5997                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
5998                }
5999                if (!shouldAdvanceFocusOnEnter()) {
6000                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6001                }
6002            }
6003            if (isMultilineInputType(outAttrs.inputType)) {
6004                // Multi-line text editors should always show an enter key.
6005                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6006            }
6007            outAttrs.hintText = mHint;
6008            if (mText instanceof Editable) {
6009                InputConnection ic = new EditableInputConnection(this);
6010                outAttrs.initialSelStart = getSelectionStart();
6011                outAttrs.initialSelEnd = getSelectionEnd();
6012                outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6013                return ic;
6014            }
6015        }
6016        return null;
6017    }
6018
6019    /**
6020     * If this TextView contains editable content, extract a portion of it
6021     * based on the information in <var>request</var> in to <var>outText</var>.
6022     * @return Returns true if the text was successfully extracted, else false.
6023     */
6024    public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6025        createEditorIfNeeded();
6026        return mEditor.extractText(request, outText);
6027    }
6028
6029    /**
6030     * This is used to remove all style-impacting spans from text before new
6031     * extracted text is being replaced into it, so that we don't have any
6032     * lingering spans applied during the replace.
6033     */
6034    static void removeParcelableSpans(Spannable spannable, int start, int end) {
6035        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6036        int i = spans.length;
6037        while (i > 0) {
6038            i--;
6039            spannable.removeSpan(spans[i]);
6040        }
6041    }
6042
6043    /**
6044     * Apply to this text view the given extracted text, as previously
6045     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6046     */
6047    public void setExtractedText(ExtractedText text) {
6048        Editable content = getEditableText();
6049        if (text.text != null) {
6050            if (content == null) {
6051                setText(text.text, TextView.BufferType.EDITABLE);
6052            } else if (text.partialStartOffset < 0) {
6053                removeParcelableSpans(content, 0, content.length());
6054                content.replace(0, content.length(), text.text);
6055            } else {
6056                final int N = content.length();
6057                int start = text.partialStartOffset;
6058                if (start > N) start = N;
6059                int end = text.partialEndOffset;
6060                if (end > N) end = N;
6061                removeParcelableSpans(content, start, end);
6062                content.replace(start, end, text.text);
6063            }
6064        }
6065
6066        // Now set the selection position...  make sure it is in range, to
6067        // avoid crashes.  If this is a partial update, it is possible that
6068        // the underlying text may have changed, causing us problems here.
6069        // Also we just don't want to trust clients to do the right thing.
6070        Spannable sp = (Spannable)getText();
6071        final int N = sp.length();
6072        int start = text.selectionStart;
6073        if (start < 0) start = 0;
6074        else if (start > N) start = N;
6075        int end = text.selectionEnd;
6076        if (end < 0) end = 0;
6077        else if (end > N) end = N;
6078        Selection.setSelection(sp, start, end);
6079
6080        // Finally, update the selection mode.
6081        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6082            MetaKeyKeyListener.startSelecting(this, sp);
6083        } else {
6084            MetaKeyKeyListener.stopSelecting(this, sp);
6085        }
6086    }
6087
6088    /**
6089     * @hide
6090     */
6091    public void setExtracting(ExtractedTextRequest req) {
6092        if (mEditor.mInputMethodState != null) {
6093            mEditor.mInputMethodState.mExtractedTextRequest = req;
6094        }
6095        // This would stop a possible selection mode, but no such mode is started in case
6096        // extracted mode will start. Some text is selected though, and will trigger an action mode
6097        // in the extracted view.
6098        mEditor.hideControllers();
6099    }
6100
6101    /**
6102     * Called by the framework in response to a text completion from
6103     * the current input method, provided by it calling
6104     * {@link InputConnection#commitCompletion
6105     * InputConnection.commitCompletion()}.  The default implementation does
6106     * nothing; text views that are supporting auto-completion should override
6107     * this to do their desired behavior.
6108     *
6109     * @param text The auto complete text the user has selected.
6110     */
6111    public void onCommitCompletion(CompletionInfo text) {
6112        // intentionally empty
6113    }
6114
6115    /**
6116     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6117     * a dictionnary) from the current input method, provided by it calling
6118     * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6119     * implementation flashes the background of the corrected word to provide feedback to the user.
6120     *
6121     * @param info The auto correct info about the text that was corrected.
6122     */
6123    public void onCommitCorrection(CorrectionInfo info) {
6124        if (mEditor != null) mEditor.onCommitCorrection(info);
6125    }
6126
6127    public void beginBatchEdit() {
6128        if (mEditor != null) mEditor.beginBatchEdit();
6129    }
6130
6131    public void endBatchEdit() {
6132        if (mEditor != null) mEditor.endBatchEdit();
6133    }
6134
6135    /**
6136     * Called by the framework in response to a request to begin a batch
6137     * of edit operations through a call to link {@link #beginBatchEdit()}.
6138     */
6139    public void onBeginBatchEdit() {
6140        // intentionally empty
6141    }
6142
6143    /**
6144     * Called by the framework in response to a request to end a batch
6145     * of edit operations through a call to link {@link #endBatchEdit}.
6146     */
6147    public void onEndBatchEdit() {
6148        // intentionally empty
6149    }
6150
6151    /**
6152     * Called by the framework in response to a private command from the
6153     * current method, provided by it calling
6154     * {@link InputConnection#performPrivateCommand
6155     * InputConnection.performPrivateCommand()}.
6156     *
6157     * @param action The action name of the command.
6158     * @param data Any additional data for the command.  This may be null.
6159     * @return Return true if you handled the command, else false.
6160     */
6161    public boolean onPrivateIMECommand(String action, Bundle data) {
6162        return false;
6163    }
6164
6165    private void nullLayouts() {
6166        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6167            mSavedLayout = (BoringLayout) mLayout;
6168        }
6169        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6170            mSavedHintLayout = (BoringLayout) mHintLayout;
6171        }
6172
6173        mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6174
6175        mBoring = mHintBoring = null;
6176
6177        // Since it depends on the value of mLayout
6178        if (mEditor != null) mEditor.prepareCursorControllers();
6179    }
6180
6181    /**
6182     * Make a new Layout based on the already-measured size of the view,
6183     * on the assumption that it was measured correctly at some point.
6184     */
6185    private void assumeLayout() {
6186        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6187
6188        if (width < 1) {
6189            width = 0;
6190        }
6191
6192        int physicalWidth = width;
6193
6194        if (mHorizontallyScrolling) {
6195            width = VERY_WIDE;
6196        }
6197
6198        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6199                      physicalWidth, false);
6200    }
6201
6202    private Layout.Alignment getLayoutAlignment() {
6203        Layout.Alignment alignment;
6204        switch (getTextAlignment()) {
6205            case TEXT_ALIGNMENT_GRAVITY:
6206                switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6207                    case Gravity.START:
6208                        alignment = Layout.Alignment.ALIGN_NORMAL;
6209                        break;
6210                    case Gravity.END:
6211                        alignment = Layout.Alignment.ALIGN_OPPOSITE;
6212                        break;
6213                    case Gravity.LEFT:
6214                        alignment = Layout.Alignment.ALIGN_LEFT;
6215                        break;
6216                    case Gravity.RIGHT:
6217                        alignment = Layout.Alignment.ALIGN_RIGHT;
6218                        break;
6219                    case Gravity.CENTER_HORIZONTAL:
6220                        alignment = Layout.Alignment.ALIGN_CENTER;
6221                        break;
6222                    default:
6223                        alignment = Layout.Alignment.ALIGN_NORMAL;
6224                        break;
6225                }
6226                break;
6227            case TEXT_ALIGNMENT_TEXT_START:
6228                alignment = Layout.Alignment.ALIGN_NORMAL;
6229                break;
6230            case TEXT_ALIGNMENT_TEXT_END:
6231                alignment = Layout.Alignment.ALIGN_OPPOSITE;
6232                break;
6233            case TEXT_ALIGNMENT_CENTER:
6234                alignment = Layout.Alignment.ALIGN_CENTER;
6235                break;
6236            case TEXT_ALIGNMENT_VIEW_START:
6237                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6238                        Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6239                break;
6240            case TEXT_ALIGNMENT_VIEW_END:
6241                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6242                        Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6243                break;
6244            case TEXT_ALIGNMENT_INHERIT:
6245                // This should never happen as we have already resolved the text alignment
6246                // but better safe than sorry so we just fall through
6247            default:
6248                alignment = Layout.Alignment.ALIGN_NORMAL;
6249                break;
6250        }
6251        return alignment;
6252    }
6253
6254    /**
6255     * The width passed in is now the desired layout width,
6256     * not the full view width with padding.
6257     * {@hide}
6258     */
6259    protected void makeNewLayout(int wantWidth, int hintWidth,
6260                                 BoringLayout.Metrics boring,
6261                                 BoringLayout.Metrics hintBoring,
6262                                 int ellipsisWidth, boolean bringIntoView) {
6263        stopMarquee();
6264
6265        // Update "old" cached values
6266        mOldMaximum = mMaximum;
6267        mOldMaxMode = mMaxMode;
6268
6269        mHighlightPathBogus = true;
6270
6271        if (wantWidth < 0) {
6272            wantWidth = 0;
6273        }
6274        if (hintWidth < 0) {
6275            hintWidth = 0;
6276        }
6277
6278        Layout.Alignment alignment = getLayoutAlignment();
6279        final boolean testDirChange = mSingleLine && mLayout != null &&
6280            (alignment == Layout.Alignment.ALIGN_NORMAL ||
6281             alignment == Layout.Alignment.ALIGN_OPPOSITE);
6282        int oldDir = 0;
6283        if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6284        boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6285        final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6286                mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6287        TruncateAt effectiveEllipsize = mEllipsize;
6288        if (mEllipsize == TruncateAt.MARQUEE &&
6289                mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6290            effectiveEllipsize = TruncateAt.END_SMALL;
6291        }
6292
6293        if (mTextDir == null) {
6294            mTextDir = getTextDirectionHeuristic();
6295        }
6296
6297        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6298                effectiveEllipsize, effectiveEllipsize == mEllipsize);
6299        if (switchEllipsize) {
6300            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6301                    TruncateAt.END : TruncateAt.MARQUEE;
6302            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6303                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6304        }
6305
6306        shouldEllipsize = mEllipsize != null;
6307        mHintLayout = null;
6308
6309        if (mHint != null) {
6310            if (shouldEllipsize) hintWidth = wantWidth;
6311
6312            if (hintBoring == UNKNOWN_BORING) {
6313                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6314                                                   mHintBoring);
6315                if (hintBoring != null) {
6316                    mHintBoring = hintBoring;
6317                }
6318            }
6319
6320            if (hintBoring != null) {
6321                if (hintBoring.width <= hintWidth &&
6322                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6323                    if (mSavedHintLayout != null) {
6324                        mHintLayout = mSavedHintLayout.
6325                                replaceOrMake(mHint, mTextPaint,
6326                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6327                                hintBoring, mIncludePad);
6328                    } else {
6329                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6330                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6331                                hintBoring, mIncludePad);
6332                    }
6333
6334                    mSavedHintLayout = (BoringLayout) mHintLayout;
6335                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6336                    if (mSavedHintLayout != null) {
6337                        mHintLayout = mSavedHintLayout.
6338                                replaceOrMake(mHint, mTextPaint,
6339                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6340                                hintBoring, mIncludePad, mEllipsize,
6341                                ellipsisWidth);
6342                    } else {
6343                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6344                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6345                                hintBoring, mIncludePad, mEllipsize,
6346                                ellipsisWidth);
6347                    }
6348                } else if (shouldEllipsize) {
6349                    mHintLayout = new StaticLayout(mHint,
6350                                0, mHint.length(),
6351                                mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6352                                mSpacingAdd, mIncludePad, mEllipsize,
6353                                ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6354                } else {
6355                    mHintLayout = new StaticLayout(mHint, mTextPaint,
6356                            hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6357                            mIncludePad);
6358                }
6359            } else if (shouldEllipsize) {
6360                mHintLayout = new StaticLayout(mHint,
6361                            0, mHint.length(),
6362                            mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6363                            mSpacingAdd, mIncludePad, mEllipsize,
6364                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6365            } else {
6366                mHintLayout = new StaticLayout(mHint, mTextPaint,
6367                        hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6368                        mIncludePad);
6369            }
6370        }
6371
6372        if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6373            registerForPreDraw();
6374        }
6375
6376        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6377            if (!compressText(ellipsisWidth)) {
6378                final int height = mLayoutParams.height;
6379                // If the size of the view does not depend on the size of the text, try to
6380                // start the marquee immediately
6381                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6382                    startMarquee();
6383                } else {
6384                    // Defer the start of the marquee until we know our width (see setFrame())
6385                    mRestartMarquee = true;
6386                }
6387            }
6388        }
6389
6390        // CursorControllers need a non-null mLayout
6391        if (mEditor != null) mEditor.prepareCursorControllers();
6392    }
6393
6394    private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6395            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6396            boolean useSaved) {
6397        Layout result = null;
6398        if (mText instanceof Spannable) {
6399            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6400                    alignment, mTextDir, mSpacingMult,
6401                    mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
6402                            ellipsisWidth);
6403        } else {
6404            if (boring == UNKNOWN_BORING) {
6405                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6406                if (boring != null) {
6407                    mBoring = boring;
6408                }
6409            }
6410
6411            if (boring != null) {
6412                if (boring.width <= wantWidth &&
6413                        (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6414                    if (useSaved && mSavedLayout != null) {
6415                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6416                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6417                                boring, mIncludePad);
6418                    } else {
6419                        result = BoringLayout.make(mTransformed, mTextPaint,
6420                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6421                                boring, mIncludePad);
6422                    }
6423
6424                    if (useSaved) {
6425                        mSavedLayout = (BoringLayout) result;
6426                    }
6427                } else if (shouldEllipsize && boring.width <= wantWidth) {
6428                    if (useSaved && mSavedLayout != null) {
6429                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6430                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6431                                boring, mIncludePad, effectiveEllipsize,
6432                                ellipsisWidth);
6433                    } else {
6434                        result = BoringLayout.make(mTransformed, mTextPaint,
6435                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6436                                boring, mIncludePad, effectiveEllipsize,
6437                                ellipsisWidth);
6438                    }
6439                } else if (shouldEllipsize) {
6440                    result = new StaticLayout(mTransformed,
6441                            0, mTransformed.length(),
6442                            mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6443                            mSpacingAdd, mIncludePad, effectiveEllipsize,
6444                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6445                } else {
6446                    result = new StaticLayout(mTransformed, mTextPaint,
6447                            wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6448                            mIncludePad);
6449                }
6450            } else if (shouldEllipsize) {
6451                result = new StaticLayout(mTransformed,
6452                        0, mTransformed.length(),
6453                        mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6454                        mSpacingAdd, mIncludePad, effectiveEllipsize,
6455                        ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6456            } else {
6457                result = new StaticLayout(mTransformed, mTextPaint,
6458                        wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6459                        mIncludePad);
6460            }
6461        }
6462        return result;
6463    }
6464
6465    private boolean compressText(float width) {
6466        if (isHardwareAccelerated()) return false;
6467
6468        // Only compress the text if it hasn't been compressed by the previous pass
6469        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6470                mTextPaint.getTextScaleX() == 1.0f) {
6471            final float textWidth = mLayout.getLineWidth(0);
6472            final float overflow = (textWidth + 1.0f - width) / width;
6473            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6474                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6475                post(new Runnable() {
6476                    public void run() {
6477                        requestLayout();
6478                    }
6479                });
6480                return true;
6481            }
6482        }
6483
6484        return false;
6485    }
6486
6487    private static int desired(Layout layout) {
6488        int n = layout.getLineCount();
6489        CharSequence text = layout.getText();
6490        float max = 0;
6491
6492        // if any line was wrapped, we can't use it.
6493        // but it's ok for the last line not to have a newline
6494
6495        for (int i = 0; i < n - 1; i++) {
6496            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6497                return -1;
6498        }
6499
6500        for (int i = 0; i < n; i++) {
6501            max = Math.max(max, layout.getLineWidth(i));
6502        }
6503
6504        return (int) Math.ceil(max);
6505    }
6506
6507    /**
6508     * Set whether the TextView includes extra top and bottom padding to make
6509     * room for accents that go above the normal ascent and descent.
6510     * The default is true.
6511     *
6512     * @see #getIncludeFontPadding()
6513     *
6514     * @attr ref android.R.styleable#TextView_includeFontPadding
6515     */
6516    public void setIncludeFontPadding(boolean includepad) {
6517        if (mIncludePad != includepad) {
6518            mIncludePad = includepad;
6519
6520            if (mLayout != null) {
6521                nullLayouts();
6522                requestLayout();
6523                invalidate();
6524            }
6525        }
6526    }
6527
6528    /**
6529     * Gets whether the TextView includes extra top and bottom padding to make
6530     * room for accents that go above the normal ascent and descent.
6531     *
6532     * @see #setIncludeFontPadding(boolean)
6533     *
6534     * @attr ref android.R.styleable#TextView_includeFontPadding
6535     */
6536    public boolean getIncludeFontPadding() {
6537        return mIncludePad;
6538    }
6539
6540    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6541
6542    @Override
6543    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6544        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6545        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6546        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6547        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6548
6549        int width;
6550        int height;
6551
6552        BoringLayout.Metrics boring = UNKNOWN_BORING;
6553        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6554
6555        if (mTextDir == null) {
6556            mTextDir = getTextDirectionHeuristic();
6557        }
6558
6559        int des = -1;
6560        boolean fromexisting = false;
6561
6562        if (widthMode == MeasureSpec.EXACTLY) {
6563            // Parent has told us how big to be. So be it.
6564            width = widthSize;
6565        } else {
6566            if (mLayout != null && mEllipsize == null) {
6567                des = desired(mLayout);
6568            }
6569
6570            if (des < 0) {
6571                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6572                if (boring != null) {
6573                    mBoring = boring;
6574                }
6575            } else {
6576                fromexisting = true;
6577            }
6578
6579            if (boring == null || boring == UNKNOWN_BORING) {
6580                if (des < 0) {
6581                    des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6582                }
6583                width = des;
6584            } else {
6585                width = boring.width;
6586            }
6587
6588            final Drawables dr = mDrawables;
6589            if (dr != null) {
6590                width = Math.max(width, dr.mDrawableWidthTop);
6591                width = Math.max(width, dr.mDrawableWidthBottom);
6592            }
6593
6594            if (mHint != null) {
6595                int hintDes = -1;
6596                int hintWidth;
6597
6598                if (mHintLayout != null && mEllipsize == null) {
6599                    hintDes = desired(mHintLayout);
6600                }
6601
6602                if (hintDes < 0) {
6603                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
6604                    if (hintBoring != null) {
6605                        mHintBoring = hintBoring;
6606                    }
6607                }
6608
6609                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6610                    if (hintDes < 0) {
6611                        hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
6612                    }
6613                    hintWidth = hintDes;
6614                } else {
6615                    hintWidth = hintBoring.width;
6616                }
6617
6618                if (hintWidth > width) {
6619                    width = hintWidth;
6620                }
6621            }
6622
6623            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6624
6625            if (mMaxWidthMode == EMS) {
6626                width = Math.min(width, mMaxWidth * getLineHeight());
6627            } else {
6628                width = Math.min(width, mMaxWidth);
6629            }
6630
6631            if (mMinWidthMode == EMS) {
6632                width = Math.max(width, mMinWidth * getLineHeight());
6633            } else {
6634                width = Math.max(width, mMinWidth);
6635            }
6636
6637            // Check against our minimum width
6638            width = Math.max(width, getSuggestedMinimumWidth());
6639
6640            if (widthMode == MeasureSpec.AT_MOST) {
6641                width = Math.min(widthSize, width);
6642            }
6643        }
6644
6645        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6646        int unpaddedWidth = want;
6647
6648        if (mHorizontallyScrolling) want = VERY_WIDE;
6649
6650        int hintWant = want;
6651        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6652
6653        if (mLayout == null) {
6654            makeNewLayout(want, hintWant, boring, hintBoring,
6655                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6656        } else {
6657            final boolean layoutChanged = (mLayout.getWidth() != want) ||
6658                    (hintWidth != hintWant) ||
6659                    (mLayout.getEllipsizedWidth() !=
6660                            width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6661
6662            final boolean widthChanged = (mHint == null) &&
6663                    (mEllipsize == null) &&
6664                    (want > mLayout.getWidth()) &&
6665                    (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6666
6667            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6668
6669            if (layoutChanged || maximumChanged) {
6670                if (!maximumChanged && widthChanged) {
6671                    mLayout.increaseWidthTo(want);
6672                } else {
6673                    makeNewLayout(want, hintWant, boring, hintBoring,
6674                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6675                }
6676            } else {
6677                // Nothing has changed
6678            }
6679        }
6680
6681        if (heightMode == MeasureSpec.EXACTLY) {
6682            // Parent has told us how big to be. So be it.
6683            height = heightSize;
6684            mDesiredHeightAtMeasure = -1;
6685        } else {
6686            int desired = getDesiredHeight();
6687
6688            height = desired;
6689            mDesiredHeightAtMeasure = desired;
6690
6691            if (heightMode == MeasureSpec.AT_MOST) {
6692                height = Math.min(desired, heightSize);
6693            }
6694        }
6695
6696        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6697        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6698            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6699        }
6700
6701        /*
6702         * We didn't let makeNewLayout() register to bring the cursor into view,
6703         * so do it here if there is any possibility that it is needed.
6704         */
6705        if (mMovement != null ||
6706            mLayout.getWidth() > unpaddedWidth ||
6707            mLayout.getHeight() > unpaddedHeight) {
6708            registerForPreDraw();
6709        } else {
6710            scrollTo(0, 0);
6711        }
6712
6713        setMeasuredDimension(width, height);
6714    }
6715
6716    private int getDesiredHeight() {
6717        return Math.max(
6718                getDesiredHeight(mLayout, true),
6719                getDesiredHeight(mHintLayout, mEllipsize != null));
6720    }
6721
6722    private int getDesiredHeight(Layout layout, boolean cap) {
6723        if (layout == null) {
6724            return 0;
6725        }
6726
6727        int linecount = layout.getLineCount();
6728        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6729        int desired = layout.getLineTop(linecount);
6730
6731        final Drawables dr = mDrawables;
6732        if (dr != null) {
6733            desired = Math.max(desired, dr.mDrawableHeightLeft);
6734            desired = Math.max(desired, dr.mDrawableHeightRight);
6735        }
6736
6737        desired += pad;
6738
6739        if (mMaxMode == LINES) {
6740            /*
6741             * Don't cap the hint to a certain number of lines.
6742             * (Do cap it, though, if we have a maximum pixel height.)
6743             */
6744            if (cap) {
6745                if (linecount > mMaximum) {
6746                    desired = layout.getLineTop(mMaximum);
6747
6748                    if (dr != null) {
6749                        desired = Math.max(desired, dr.mDrawableHeightLeft);
6750                        desired = Math.max(desired, dr.mDrawableHeightRight);
6751                    }
6752
6753                    desired += pad;
6754                    linecount = mMaximum;
6755                }
6756            }
6757        } else {
6758            desired = Math.min(desired, mMaximum);
6759        }
6760
6761        if (mMinMode == LINES) {
6762            if (linecount < mMinimum) {
6763                desired += getLineHeight() * (mMinimum - linecount);
6764            }
6765        } else {
6766            desired = Math.max(desired, mMinimum);
6767        }
6768
6769        // Check against our minimum height
6770        desired = Math.max(desired, getSuggestedMinimumHeight());
6771
6772        return desired;
6773    }
6774
6775    /**
6776     * Check whether a change to the existing text layout requires a
6777     * new view layout.
6778     */
6779    private void checkForResize() {
6780        boolean sizeChanged = false;
6781
6782        if (mLayout != null) {
6783            // Check if our width changed
6784            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6785                sizeChanged = true;
6786                invalidate();
6787            }
6788
6789            // Check if our height changed
6790            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6791                int desiredHeight = getDesiredHeight();
6792
6793                if (desiredHeight != this.getHeight()) {
6794                    sizeChanged = true;
6795                }
6796            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6797                if (mDesiredHeightAtMeasure >= 0) {
6798                    int desiredHeight = getDesiredHeight();
6799
6800                    if (desiredHeight != mDesiredHeightAtMeasure) {
6801                        sizeChanged = true;
6802                    }
6803                }
6804            }
6805        }
6806
6807        if (sizeChanged) {
6808            requestLayout();
6809            // caller will have already invalidated
6810        }
6811    }
6812
6813    /**
6814     * Check whether entirely new text requires a new view layout
6815     * or merely a new text layout.
6816     */
6817    private void checkForRelayout() {
6818        // If we have a fixed width, we can just swap in a new text layout
6819        // if the text height stays the same or if the view height is fixed.
6820
6821        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6822                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6823                (mHint == null || mHintLayout != null) &&
6824                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6825            // Static width, so try making a new text layout.
6826
6827            int oldht = mLayout.getHeight();
6828            int want = mLayout.getWidth();
6829            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6830
6831            /*
6832             * No need to bring the text into view, since the size is not
6833             * changing (unless we do the requestLayout(), in which case it
6834             * will happen at measure).
6835             */
6836            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6837                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6838                          false);
6839
6840            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6841                // In a fixed-height view, so use our new text layout.
6842                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6843                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6844                    invalidate();
6845                    return;
6846                }
6847
6848                // Dynamic height, but height has stayed the same,
6849                // so use our new text layout.
6850                if (mLayout.getHeight() == oldht &&
6851                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6852                    invalidate();
6853                    return;
6854                }
6855            }
6856
6857            // We lose: the height has changed and we have a dynamic height.
6858            // Request a new view layout using our new text layout.
6859            requestLayout();
6860            invalidate();
6861        } else {
6862            // Dynamic width, so we have no choice but to request a new
6863            // view layout with a new text layout.
6864            nullLayouts();
6865            requestLayout();
6866            invalidate();
6867        }
6868    }
6869
6870    @Override
6871    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6872        super.onLayout(changed, left, top, right, bottom);
6873        if (mDeferScroll >= 0) {
6874            int curs = mDeferScroll;
6875            mDeferScroll = -1;
6876            bringPointIntoView(Math.min(curs, mText.length()));
6877        }
6878    }
6879
6880    private boolean isShowingHint() {
6881        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6882    }
6883
6884    /**
6885     * Returns true if anything changed.
6886     */
6887    private boolean bringTextIntoView() {
6888        Layout layout = isShowingHint() ? mHintLayout : mLayout;
6889        int line = 0;
6890        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6891            line = layout.getLineCount() - 1;
6892        }
6893
6894        Layout.Alignment a = layout.getParagraphAlignment(line);
6895        int dir = layout.getParagraphDirection(line);
6896        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6897        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6898        int ht = layout.getHeight();
6899
6900        int scrollx, scrolly;
6901
6902        // Convert to left, center, or right alignment.
6903        if (a == Layout.Alignment.ALIGN_NORMAL) {
6904            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6905                Layout.Alignment.ALIGN_RIGHT;
6906        } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6907            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6908                Layout.Alignment.ALIGN_LEFT;
6909        }
6910
6911        if (a == Layout.Alignment.ALIGN_CENTER) {
6912            /*
6913             * Keep centered if possible, or, if it is too wide to fit,
6914             * keep leading edge in view.
6915             */
6916
6917            int left = (int) Math.floor(layout.getLineLeft(line));
6918            int right = (int) Math.ceil(layout.getLineRight(line));
6919
6920            if (right - left < hspace) {
6921                scrollx = (right + left) / 2 - hspace / 2;
6922            } else {
6923                if (dir < 0) {
6924                    scrollx = right - hspace;
6925                } else {
6926                    scrollx = left;
6927                }
6928            }
6929        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
6930            int right = (int) Math.ceil(layout.getLineRight(line));
6931            scrollx = right - hspace;
6932        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
6933            scrollx = (int) Math.floor(layout.getLineLeft(line));
6934        }
6935
6936        if (ht < vspace) {
6937            scrolly = 0;
6938        } else {
6939            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6940                scrolly = ht - vspace;
6941            } else {
6942                scrolly = 0;
6943            }
6944        }
6945
6946        if (scrollx != mScrollX || scrolly != mScrollY) {
6947            scrollTo(scrollx, scrolly);
6948            return true;
6949        } else {
6950            return false;
6951        }
6952    }
6953
6954    /**
6955     * Move the point, specified by the offset, into the view if it is needed.
6956     * This has to be called after layout. Returns true if anything changed.
6957     */
6958    public boolean bringPointIntoView(int offset) {
6959        if (isLayoutRequested()) {
6960            mDeferScroll = offset;
6961            return false;
6962        }
6963        boolean changed = false;
6964
6965        Layout layout = isShowingHint() ? mHintLayout: mLayout;
6966
6967        if (layout == null) return changed;
6968
6969        int line = layout.getLineForOffset(offset);
6970
6971        int grav;
6972
6973        switch (layout.getParagraphAlignment(line)) {
6974            case ALIGN_LEFT:
6975                grav = 1;
6976                break;
6977            case ALIGN_RIGHT:
6978                grav = -1;
6979                break;
6980            case ALIGN_NORMAL:
6981                grav = layout.getParagraphDirection(line);
6982                break;
6983            case ALIGN_OPPOSITE:
6984                grav = -layout.getParagraphDirection(line);
6985                break;
6986            case ALIGN_CENTER:
6987            default:
6988                grav = 0;
6989                break;
6990        }
6991
6992        // We only want to clamp the cursor to fit within the layout width
6993        // in left-to-right modes, because in a right to left alignment,
6994        // we want to scroll to keep the line-right on the screen, as other
6995        // lines are likely to have text flush with the right margin, which
6996        // we want to keep visible.
6997        // A better long-term solution would probably be to measure both
6998        // the full line and a blank-trimmed version, and, for example, use
6999        // the latter measurement for centering and right alignment, but for
7000        // the time being we only implement the cursor clamping in left to
7001        // right where it is most likely to be annoying.
7002        final boolean clamped = grav > 0;
7003        // FIXME: Is it okay to truncate this, or should we round?
7004        final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7005        final int top = layout.getLineTop(line);
7006        final int bottom = layout.getLineTop(line + 1);
7007
7008        int left = (int) Math.floor(layout.getLineLeft(line));
7009        int right = (int) Math.ceil(layout.getLineRight(line));
7010        int ht = layout.getHeight();
7011
7012        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7013        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7014        if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7015            // If cursor has been clamped, make sure we don't scroll.
7016            right = Math.max(x, left + hspace);
7017        }
7018
7019        int hslack = (bottom - top) / 2;
7020        int vslack = hslack;
7021
7022        if (vslack > vspace / 4)
7023            vslack = vspace / 4;
7024        if (hslack > hspace / 4)
7025            hslack = hspace / 4;
7026
7027        int hs = mScrollX;
7028        int vs = mScrollY;
7029
7030        if (top - vs < vslack)
7031            vs = top - vslack;
7032        if (bottom - vs > vspace - vslack)
7033            vs = bottom - (vspace - vslack);
7034        if (ht - vs < vspace)
7035            vs = ht - vspace;
7036        if (0 - vs > 0)
7037            vs = 0;
7038
7039        if (grav != 0) {
7040            if (x - hs < hslack) {
7041                hs = x - hslack;
7042            }
7043            if (x - hs > hspace - hslack) {
7044                hs = x - (hspace - hslack);
7045            }
7046        }
7047
7048        if (grav < 0) {
7049            if (left - hs > 0)
7050                hs = left;
7051            if (right - hs < hspace)
7052                hs = right - hspace;
7053        } else if (grav > 0) {
7054            if (right - hs < hspace)
7055                hs = right - hspace;
7056            if (left - hs > 0)
7057                hs = left;
7058        } else /* grav == 0 */ {
7059            if (right - left <= hspace) {
7060                /*
7061                 * If the entire text fits, center it exactly.
7062                 */
7063                hs = left - (hspace - (right - left)) / 2;
7064            } else if (x > right - hslack) {
7065                /*
7066                 * If we are near the right edge, keep the right edge
7067                 * at the edge of the view.
7068                 */
7069                hs = right - hspace;
7070            } else if (x < left + hslack) {
7071                /*
7072                 * If we are near the left edge, keep the left edge
7073                 * at the edge of the view.
7074                 */
7075                hs = left;
7076            } else if (left > hs) {
7077                /*
7078                 * Is there whitespace visible at the left?  Fix it if so.
7079                 */
7080                hs = left;
7081            } else if (right < hs + hspace) {
7082                /*
7083                 * Is there whitespace visible at the right?  Fix it if so.
7084                 */
7085                hs = right - hspace;
7086            } else {
7087                /*
7088                 * Otherwise, float as needed.
7089                 */
7090                if (x - hs < hslack) {
7091                    hs = x - hslack;
7092                }
7093                if (x - hs > hspace - hslack) {
7094                    hs = x - (hspace - hslack);
7095                }
7096            }
7097        }
7098
7099        if (hs != mScrollX || vs != mScrollY) {
7100            if (mScroller == null) {
7101                scrollTo(hs, vs);
7102            } else {
7103                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7104                int dx = hs - mScrollX;
7105                int dy = vs - mScrollY;
7106
7107                if (duration > ANIMATED_SCROLL_GAP) {
7108                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7109                    awakenScrollBars(mScroller.getDuration());
7110                    invalidate();
7111                } else {
7112                    if (!mScroller.isFinished()) {
7113                        mScroller.abortAnimation();
7114                    }
7115
7116                    scrollBy(dx, dy);
7117                }
7118
7119                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7120            }
7121
7122            changed = true;
7123        }
7124
7125        if (isFocused()) {
7126            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7127            // requestRectangleOnScreen() is in terms of content coordinates.
7128
7129            // The offsets here are to ensure the rectangle we are using is
7130            // within our view bounds, in case the cursor is on the far left
7131            // or right.  If it isn't withing the bounds, then this request
7132            // will be ignored.
7133            if (mTempRect == null) mTempRect = new Rect();
7134            mTempRect.set(x - 2, top, x + 2, bottom);
7135            getInterestingRect(mTempRect, line);
7136            mTempRect.offset(mScrollX, mScrollY);
7137
7138            if (requestRectangleOnScreen(mTempRect)) {
7139                changed = true;
7140            }
7141        }
7142
7143        return changed;
7144    }
7145
7146    /**
7147     * Move the cursor, if needed, so that it is at an offset that is visible
7148     * to the user.  This will not move the cursor if it represents more than
7149     * one character (a selection range).  This will only work if the
7150     * TextView contains spannable text; otherwise it will do nothing.
7151     *
7152     * @return True if the cursor was actually moved, false otherwise.
7153     */
7154    public boolean moveCursorToVisibleOffset() {
7155        if (!(mText instanceof Spannable)) {
7156            return false;
7157        }
7158        int start = getSelectionStart();
7159        int end = getSelectionEnd();
7160        if (start != end) {
7161            return false;
7162        }
7163
7164        // First: make sure the line is visible on screen:
7165
7166        int line = mLayout.getLineForOffset(start);
7167
7168        final int top = mLayout.getLineTop(line);
7169        final int bottom = mLayout.getLineTop(line + 1);
7170        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7171        int vslack = (bottom - top) / 2;
7172        if (vslack > vspace / 4)
7173            vslack = vspace / 4;
7174        final int vs = mScrollY;
7175
7176        if (top < (vs+vslack)) {
7177            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7178        } else if (bottom > (vspace+vs-vslack)) {
7179            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7180        }
7181
7182        // Next: make sure the character is visible on screen:
7183
7184        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7185        final int hs = mScrollX;
7186        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7187        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7188
7189        // line might contain bidirectional text
7190        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7191        final int highChar = leftChar > rightChar ? leftChar : rightChar;
7192
7193        int newStart = start;
7194        if (newStart < lowChar) {
7195            newStart = lowChar;
7196        } else if (newStart > highChar) {
7197            newStart = highChar;
7198        }
7199
7200        if (newStart != start) {
7201            Selection.setSelection((Spannable)mText, newStart);
7202            return true;
7203        }
7204
7205        return false;
7206    }
7207
7208    @Override
7209    public void computeScroll() {
7210        if (mScroller != null) {
7211            if (mScroller.computeScrollOffset()) {
7212                mScrollX = mScroller.getCurrX();
7213                mScrollY = mScroller.getCurrY();
7214                invalidateParentCaches();
7215                postInvalidate();  // So we draw again
7216            }
7217        }
7218    }
7219
7220    private void getInterestingRect(Rect r, int line) {
7221        convertFromViewportToContentCoordinates(r);
7222
7223        // Rectangle can can be expanded on first and last line to take
7224        // padding into account.
7225        // TODO Take left/right padding into account too?
7226        if (line == 0) r.top -= getExtendedPaddingTop();
7227        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7228    }
7229
7230    private void convertFromViewportToContentCoordinates(Rect r) {
7231        final int horizontalOffset = viewportToContentHorizontalOffset();
7232        r.left += horizontalOffset;
7233        r.right += horizontalOffset;
7234
7235        final int verticalOffset = viewportToContentVerticalOffset();
7236        r.top += verticalOffset;
7237        r.bottom += verticalOffset;
7238    }
7239
7240    int viewportToContentHorizontalOffset() {
7241        return getCompoundPaddingLeft() - mScrollX;
7242    }
7243
7244    int viewportToContentVerticalOffset() {
7245        int offset = getExtendedPaddingTop() - mScrollY;
7246        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7247            offset += getVerticalOffset(false);
7248        }
7249        return offset;
7250    }
7251
7252    @Override
7253    public void debug(int depth) {
7254        super.debug(depth);
7255
7256        String output = debugIndent(depth);
7257        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7258                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7259                + "} ";
7260
7261        if (mText != null) {
7262
7263            output += "mText=\"" + mText + "\" ";
7264            if (mLayout != null) {
7265                output += "mLayout width=" + mLayout.getWidth()
7266                        + " height=" + mLayout.getHeight();
7267            }
7268        } else {
7269            output += "mText=NULL";
7270        }
7271        Log.d(VIEW_LOG_TAG, output);
7272    }
7273
7274    /**
7275     * Convenience for {@link Selection#getSelectionStart}.
7276     */
7277    @ViewDebug.ExportedProperty(category = "text")
7278    public int getSelectionStart() {
7279        return Selection.getSelectionStart(getText());
7280    }
7281
7282    /**
7283     * Convenience for {@link Selection#getSelectionEnd}.
7284     */
7285    @ViewDebug.ExportedProperty(category = "text")
7286    public int getSelectionEnd() {
7287        return Selection.getSelectionEnd(getText());
7288    }
7289
7290    /**
7291     * Return true iff there is a selection inside this text view.
7292     */
7293    public boolean hasSelection() {
7294        final int selectionStart = getSelectionStart();
7295        final int selectionEnd = getSelectionEnd();
7296
7297        return selectionStart >= 0 && selectionStart != selectionEnd;
7298    }
7299
7300    /**
7301     * Sets the properties of this field (lines, horizontally scrolling,
7302     * transformation method) to be for a single-line input.
7303     *
7304     * @attr ref android.R.styleable#TextView_singleLine
7305     */
7306    public void setSingleLine() {
7307        setSingleLine(true);
7308    }
7309
7310    /**
7311     * Sets the properties of this field to transform input to ALL CAPS
7312     * display. This may use a "small caps" formatting if available.
7313     * This setting will be ignored if this field is editable or selectable.
7314     *
7315     * This call replaces the current transformation method. Disabling this
7316     * will not necessarily restore the previous behavior from before this
7317     * was enabled.
7318     *
7319     * @see #setTransformationMethod(TransformationMethod)
7320     * @attr ref android.R.styleable#TextView_textAllCaps
7321     */
7322    public void setAllCaps(boolean allCaps) {
7323        if (allCaps) {
7324            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7325        } else {
7326            setTransformationMethod(null);
7327        }
7328    }
7329
7330    /**
7331     * If true, sets the properties of this field (number of lines, horizontally scrolling,
7332     * transformation method) to be for a single-line input; if false, restores these to the default
7333     * conditions.
7334     *
7335     * Note that the default conditions are not necessarily those that were in effect prior this
7336     * method, and you may want to reset these properties to your custom values.
7337     *
7338     * @attr ref android.R.styleable#TextView_singleLine
7339     */
7340    @android.view.RemotableViewMethod
7341    public void setSingleLine(boolean singleLine) {
7342        // Could be used, but may break backward compatibility.
7343        // if (mSingleLine == singleLine) return;
7344        setInputTypeSingleLine(singleLine);
7345        applySingleLine(singleLine, true, true);
7346    }
7347
7348    /**
7349     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7350     * @param singleLine
7351     */
7352    private void setInputTypeSingleLine(boolean singleLine) {
7353        if (mEditor != null &&
7354                (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7355            if (singleLine) {
7356                mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7357            } else {
7358                mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7359            }
7360        }
7361    }
7362
7363    private void applySingleLine(boolean singleLine, boolean applyTransformation,
7364            boolean changeMaxLines) {
7365        mSingleLine = singleLine;
7366        if (singleLine) {
7367            setLines(1);
7368            setHorizontallyScrolling(true);
7369            if (applyTransformation) {
7370                setTransformationMethod(SingleLineTransformationMethod.getInstance());
7371            }
7372        } else {
7373            if (changeMaxLines) {
7374                setMaxLines(Integer.MAX_VALUE);
7375            }
7376            setHorizontallyScrolling(false);
7377            if (applyTransformation) {
7378                setTransformationMethod(null);
7379            }
7380        }
7381    }
7382
7383    /**
7384     * Causes words in the text that are longer than the view is wide
7385     * to be ellipsized instead of broken in the middle.  You may also
7386     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7387     * to constrain the text to a single line.  Use <code>null</code>
7388     * to turn off ellipsizing.
7389     *
7390     * If {@link #setMaxLines} has been used to set two or more lines,
7391     * only {@link android.text.TextUtils.TruncateAt#END} and
7392     * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7393     * (other ellipsizing types will not do anything).
7394     *
7395     * @attr ref android.R.styleable#TextView_ellipsize
7396     */
7397    public void setEllipsize(TextUtils.TruncateAt where) {
7398        // TruncateAt is an enum. != comparison is ok between these singleton objects.
7399        if (mEllipsize != where) {
7400            mEllipsize = where;
7401
7402            if (mLayout != null) {
7403                nullLayouts();
7404                requestLayout();
7405                invalidate();
7406            }
7407        }
7408    }
7409
7410    /**
7411     * Sets how many times to repeat the marquee animation. Only applied if the
7412     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7413     *
7414     * @see #getMarqueeRepeatLimit()
7415     *
7416     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7417     */
7418    public void setMarqueeRepeatLimit(int marqueeLimit) {
7419        mMarqueeRepeatLimit = marqueeLimit;
7420    }
7421
7422    /**
7423     * Gets the number of times the marquee animation is repeated. Only meaningful if the
7424     * TextView has marquee enabled.
7425     *
7426     * @return the number of times the marquee animation is repeated. -1 if the animation
7427     * repeats indefinitely
7428     *
7429     * @see #setMarqueeRepeatLimit(int)
7430     *
7431     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7432     */
7433    public int getMarqueeRepeatLimit() {
7434        return mMarqueeRepeatLimit;
7435    }
7436
7437    /**
7438     * Returns where, if anywhere, words that are longer than the view
7439     * is wide should be ellipsized.
7440     */
7441    @ViewDebug.ExportedProperty
7442    public TextUtils.TruncateAt getEllipsize() {
7443        return mEllipsize;
7444    }
7445
7446    /**
7447     * Set the TextView so that when it takes focus, all the text is
7448     * selected.
7449     *
7450     * @attr ref android.R.styleable#TextView_selectAllOnFocus
7451     */
7452    @android.view.RemotableViewMethod
7453    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7454        createEditorIfNeeded();
7455        mEditor.mSelectAllOnFocus = selectAllOnFocus;
7456
7457        if (selectAllOnFocus && !(mText instanceof Spannable)) {
7458            setText(mText, BufferType.SPANNABLE);
7459        }
7460    }
7461
7462    /**
7463     * Set whether the cursor is visible. The default is true. Note that this property only
7464     * makes sense for editable TextView.
7465     *
7466     * @see #isCursorVisible()
7467     *
7468     * @attr ref android.R.styleable#TextView_cursorVisible
7469     */
7470    @android.view.RemotableViewMethod
7471    public void setCursorVisible(boolean visible) {
7472        if (visible && mEditor == null) return; // visible is the default value with no edit data
7473        createEditorIfNeeded();
7474        if (mEditor.mCursorVisible != visible) {
7475            mEditor.mCursorVisible = visible;
7476            invalidate();
7477
7478            mEditor.makeBlink();
7479
7480            // InsertionPointCursorController depends on mCursorVisible
7481            mEditor.prepareCursorControllers();
7482        }
7483    }
7484
7485    /**
7486     * @return whether or not the cursor is visible (assuming this TextView is editable)
7487     *
7488     * @see #setCursorVisible(boolean)
7489     *
7490     * @attr ref android.R.styleable#TextView_cursorVisible
7491     */
7492    public boolean isCursorVisible() {
7493        // true is the default value
7494        return mEditor == null ? true : mEditor.mCursorVisible;
7495    }
7496
7497    private boolean canMarquee() {
7498        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7499        return width > 0 && (mLayout.getLineWidth(0) > width ||
7500                (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7501                        mSavedMarqueeModeLayout.getLineWidth(0) > width));
7502    }
7503
7504    private void startMarquee() {
7505        // Do not ellipsize EditText
7506        if (getKeyListener() != null) return;
7507
7508        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7509            return;
7510        }
7511
7512        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7513                getLineCount() == 1 && canMarquee()) {
7514
7515            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7516                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7517                final Layout tmp = mLayout;
7518                mLayout = mSavedMarqueeModeLayout;
7519                mSavedMarqueeModeLayout = tmp;
7520                setHorizontalFadingEdgeEnabled(true);
7521                requestLayout();
7522                invalidate();
7523            }
7524
7525            if (mMarquee == null) mMarquee = new Marquee(this);
7526            mMarquee.start(mMarqueeRepeatLimit);
7527        }
7528    }
7529
7530    private void stopMarquee() {
7531        if (mMarquee != null && !mMarquee.isStopped()) {
7532            mMarquee.stop();
7533        }
7534
7535        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7536            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7537            final Layout tmp = mSavedMarqueeModeLayout;
7538            mSavedMarqueeModeLayout = mLayout;
7539            mLayout = tmp;
7540            setHorizontalFadingEdgeEnabled(false);
7541            requestLayout();
7542            invalidate();
7543        }
7544    }
7545
7546    private void startStopMarquee(boolean start) {
7547        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7548            if (start) {
7549                startMarquee();
7550            } else {
7551                stopMarquee();
7552            }
7553        }
7554    }
7555
7556    /**
7557     * This method is called when the text is changed, in case any subclasses
7558     * would like to know.
7559     *
7560     * Within <code>text</code>, the <code>lengthAfter</code> characters
7561     * beginning at <code>start</code> have just replaced old text that had
7562     * length <code>lengthBefore</code>. It is an error to attempt to make
7563     * changes to <code>text</code> from this callback.
7564     *
7565     * @param text The text the TextView is displaying
7566     * @param start The offset of the start of the range of the text that was
7567     * modified
7568     * @param lengthBefore The length of the former text that has been replaced
7569     * @param lengthAfter The length of the replacement modified text
7570     */
7571    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7572        // intentionally empty, template pattern method can be overridden by subclasses
7573    }
7574
7575    /**
7576     * This method is called when the selection has changed, in case any
7577     * subclasses would like to know.
7578     *
7579     * @param selStart The new selection start location.
7580     * @param selEnd The new selection end location.
7581     */
7582    protected void onSelectionChanged(int selStart, int selEnd) {
7583        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7584    }
7585
7586    /**
7587     * Adds a TextWatcher to the list of those whose methods are called
7588     * whenever this TextView's text changes.
7589     * <p>
7590     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7591     * not called after {@link #setText} calls.  Now, doing {@link #setText}
7592     * if there are any text changed listeners forces the buffer type to
7593     * Editable if it would not otherwise be and does call this method.
7594     */
7595    public void addTextChangedListener(TextWatcher watcher) {
7596        if (mListeners == null) {
7597            mListeners = new ArrayList<TextWatcher>();
7598        }
7599
7600        mListeners.add(watcher);
7601    }
7602
7603    /**
7604     * Removes the specified TextWatcher from the list of those whose
7605     * methods are called
7606     * whenever this TextView's text changes.
7607     */
7608    public void removeTextChangedListener(TextWatcher watcher) {
7609        if (mListeners != null) {
7610            int i = mListeners.indexOf(watcher);
7611
7612            if (i >= 0) {
7613                mListeners.remove(i);
7614            }
7615        }
7616    }
7617
7618    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7619        if (mListeners != null) {
7620            final ArrayList<TextWatcher> list = mListeners;
7621            final int count = list.size();
7622            for (int i = 0; i < count; i++) {
7623                list.get(i).beforeTextChanged(text, start, before, after);
7624            }
7625        }
7626
7627        // The spans that are inside or intersect the modified region no longer make sense
7628        removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7629        removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
7630    }
7631
7632    // Removes all spans that are inside or actually overlap the start..end range
7633    private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
7634        if (!(mText instanceof Editable)) return;
7635        Editable text = (Editable) mText;
7636
7637        T[] spans = text.getSpans(start, end, type);
7638        final int length = spans.length;
7639        for (int i = 0; i < length; i++) {
7640            final int spanStart = text.getSpanStart(spans[i]);
7641            final int spanEnd = text.getSpanEnd(spans[i]);
7642            if (spanEnd == start || spanStart == end) break;
7643            text.removeSpan(spans[i]);
7644        }
7645    }
7646
7647    void removeAdjacentSuggestionSpans(final int pos) {
7648        if (!(mText instanceof Editable)) return;
7649        final Editable text = (Editable) mText;
7650
7651        final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7652        final int length = spans.length;
7653        for (int i = 0; i < length; i++) {
7654            final int spanStart = text.getSpanStart(spans[i]);
7655            final int spanEnd = text.getSpanEnd(spans[i]);
7656            if (spanEnd == pos || spanStart == pos) {
7657                if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7658                    text.removeSpan(spans[i]);
7659                }
7660            }
7661        }
7662    }
7663
7664    /**
7665     * Not private so it can be called from an inner class without going
7666     * through a thunk.
7667     */
7668    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7669        if (mListeners != null) {
7670            final ArrayList<TextWatcher> list = mListeners;
7671            final int count = list.size();
7672            for (int i = 0; i < count; i++) {
7673                list.get(i).onTextChanged(text, start, before, after);
7674            }
7675        }
7676
7677        if (mEditor != null) mEditor.sendOnTextChanged(start, after);
7678    }
7679
7680    /**
7681     * Not private so it can be called from an inner class without going
7682     * through a thunk.
7683     */
7684    void sendAfterTextChanged(Editable text) {
7685        if (mListeners != null) {
7686            final ArrayList<TextWatcher> list = mListeners;
7687            final int count = list.size();
7688            for (int i = 0; i < count; i++) {
7689                list.get(i).afterTextChanged(text);
7690            }
7691        }
7692        hideErrorIfUnchanged();
7693    }
7694
7695    void updateAfterEdit() {
7696        invalidate();
7697        int curs = getSelectionStart();
7698
7699        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7700            registerForPreDraw();
7701        }
7702
7703        checkForResize();
7704
7705        if (curs >= 0) {
7706            mHighlightPathBogus = true;
7707            if (mEditor != null) mEditor.makeBlink();
7708            bringPointIntoView(curs);
7709        }
7710    }
7711
7712    /**
7713     * Not private so it can be called from an inner class without going
7714     * through a thunk.
7715     */
7716    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
7717        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
7718        if (ims == null || ims.mBatchEditNesting == 0) {
7719            updateAfterEdit();
7720        }
7721        if (ims != null) {
7722            ims.mContentChanged = true;
7723            if (ims.mChangedStart < 0) {
7724                ims.mChangedStart = start;
7725                ims.mChangedEnd = start+before;
7726            } else {
7727                ims.mChangedStart = Math.min(ims.mChangedStart, start);
7728                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
7729            }
7730            ims.mChangedDelta += after-before;
7731        }
7732        resetErrorChangedFlag();
7733        sendOnTextChanged(buffer, start, before, after);
7734        onTextChanged(buffer, start, before, after);
7735    }
7736
7737    /**
7738     * Not private so it can be called from an inner class without going
7739     * through a thunk.
7740     */
7741    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
7742        // XXX Make the start and end move together if this ends up
7743        // spending too much time invalidating.
7744
7745        boolean selChanged = false;
7746        int newSelStart=-1, newSelEnd=-1;
7747
7748        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
7749
7750        if (what == Selection.SELECTION_END) {
7751            selChanged = true;
7752            newSelEnd = newStart;
7753
7754            if (oldStart >= 0 || newStart >= 0) {
7755                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
7756                checkForResize();
7757                registerForPreDraw();
7758                if (mEditor != null) mEditor.makeBlink();
7759            }
7760        }
7761
7762        if (what == Selection.SELECTION_START) {
7763            selChanged = true;
7764            newSelStart = newStart;
7765
7766            if (oldStart >= 0 || newStart >= 0) {
7767                int end = Selection.getSelectionEnd(buf);
7768                invalidateCursor(end, oldStart, newStart);
7769            }
7770        }
7771
7772        if (selChanged) {
7773            mHighlightPathBogus = true;
7774            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
7775
7776            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7777                if (newSelStart < 0) {
7778                    newSelStart = Selection.getSelectionStart(buf);
7779                }
7780                if (newSelEnd < 0) {
7781                    newSelEnd = Selection.getSelectionEnd(buf);
7782                }
7783                onSelectionChanged(newSelStart, newSelEnd);
7784            }
7785        }
7786
7787        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7788                what instanceof CharacterStyle) {
7789            if (ims == null || ims.mBatchEditNesting == 0) {
7790                invalidate();
7791                mHighlightPathBogus = true;
7792                checkForResize();
7793            } else {
7794                ims.mContentChanged = true;
7795            }
7796            if (mEditor != null) {
7797                if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7798                if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
7799            }
7800        }
7801
7802        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7803            mHighlightPathBogus = true;
7804            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7805                ims.mSelectionModeChanged = true;
7806            }
7807
7808            if (Selection.getSelectionStart(buf) >= 0) {
7809                if (ims == null || ims.mBatchEditNesting == 0) {
7810                    invalidateCursor();
7811                } else {
7812                    ims.mCursorChanged = true;
7813                }
7814            }
7815        }
7816
7817        if (what instanceof ParcelableSpan) {
7818            // If this is a span that can be sent to a remote process,
7819            // the current extract editor would be interested in it.
7820            if (ims != null && ims.mExtractedTextRequest != null) {
7821                if (ims.mBatchEditNesting != 0) {
7822                    if (oldStart >= 0) {
7823                        if (ims.mChangedStart > oldStart) {
7824                            ims.mChangedStart = oldStart;
7825                        }
7826                        if (ims.mChangedStart > oldEnd) {
7827                            ims.mChangedStart = oldEnd;
7828                        }
7829                    }
7830                    if (newStart >= 0) {
7831                        if (ims.mChangedStart > newStart) {
7832                            ims.mChangedStart = newStart;
7833                        }
7834                        if (ims.mChangedStart > newEnd) {
7835                            ims.mChangedStart = newEnd;
7836                        }
7837                    }
7838                } else {
7839                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7840                            + oldStart + "-" + oldEnd + ","
7841                            + newStart + "-" + newEnd + " " + what);
7842                    ims.mContentChanged = true;
7843                }
7844            }
7845        }
7846
7847        if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7848                what instanceof SpellCheckSpan) {
7849            mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
7850        }
7851    }
7852
7853    /**
7854     * @hide
7855     */
7856    @Override
7857    public void dispatchFinishTemporaryDetach() {
7858        mDispatchTemporaryDetach = true;
7859        super.dispatchFinishTemporaryDetach();
7860        mDispatchTemporaryDetach = false;
7861    }
7862
7863    @Override
7864    public void onStartTemporaryDetach() {
7865        super.onStartTemporaryDetach();
7866        // Only track when onStartTemporaryDetach() is called directly,
7867        // usually because this instance is an editable field in a list
7868        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
7869
7870        // Tell the editor that we are temporarily detached. It can use this to preserve
7871        // selection state as needed.
7872        if (mEditor != null) mEditor.mTemporaryDetach = true;
7873    }
7874
7875    @Override
7876    public void onFinishTemporaryDetach() {
7877        super.onFinishTemporaryDetach();
7878        // Only track when onStartTemporaryDetach() is called directly,
7879        // usually because this instance is an editable field in a list
7880        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
7881        if (mEditor != null) mEditor.mTemporaryDetach = false;
7882    }
7883
7884    @Override
7885    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7886        if (mTemporaryDetach) {
7887            // If we are temporarily in the detach state, then do nothing.
7888            super.onFocusChanged(focused, direction, previouslyFocusedRect);
7889            return;
7890        }
7891
7892        if (mEditor != null) mEditor.onFocusChanged(focused, direction);
7893
7894        if (focused) {
7895            if (mText instanceof Spannable) {
7896                Spannable sp = (Spannable) mText;
7897                MetaKeyKeyListener.resetMetaState(sp);
7898            }
7899        }
7900
7901        startStopMarquee(focused);
7902
7903        if (mTransformation != null) {
7904            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
7905        }
7906
7907        super.onFocusChanged(focused, direction, previouslyFocusedRect);
7908    }
7909
7910    @Override
7911    public void onWindowFocusChanged(boolean hasWindowFocus) {
7912        super.onWindowFocusChanged(hasWindowFocus);
7913
7914        if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
7915
7916        startStopMarquee(hasWindowFocus);
7917    }
7918
7919    @Override
7920    protected void onVisibilityChanged(View changedView, int visibility) {
7921        super.onVisibilityChanged(changedView, visibility);
7922        if (mEditor != null && visibility != VISIBLE) {
7923            mEditor.hideControllers();
7924        }
7925    }
7926
7927    /**
7928     * Use {@link BaseInputConnection#removeComposingSpans
7929     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7930     * state from this text view.
7931     */
7932    public void clearComposingText() {
7933        if (mText instanceof Spannable) {
7934            BaseInputConnection.removeComposingSpans((Spannable)mText);
7935        }
7936    }
7937
7938    @Override
7939    public void setSelected(boolean selected) {
7940        boolean wasSelected = isSelected();
7941
7942        super.setSelected(selected);
7943
7944        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7945            if (selected) {
7946                startMarquee();
7947            } else {
7948                stopMarquee();
7949            }
7950        }
7951    }
7952
7953    @Override
7954    public boolean onTouchEvent(MotionEvent event) {
7955        final int action = event.getActionMasked();
7956
7957        if (mEditor != null) mEditor.onTouchEvent(event);
7958
7959        final boolean superResult = super.onTouchEvent(event);
7960
7961        /*
7962         * Don't handle the release after a long press, because it will
7963         * move the selection away from whatever the menu action was
7964         * trying to affect.
7965         */
7966        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7967            mEditor.mDiscardNextActionUp = false;
7968            return superResult;
7969        }
7970
7971        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
7972                (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
7973
7974         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7975                && mText instanceof Spannable && mLayout != null) {
7976            boolean handled = false;
7977
7978            if (mMovement != null) {
7979                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7980            }
7981
7982            final boolean textIsSelectable = isTextSelectable();
7983            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
7984                // The LinkMovementMethod which should handle taps on links has not been installed
7985                // on non editable text that support text selection.
7986                // We reproduce its behavior here to open links for these.
7987                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7988                        getSelectionEnd(), ClickableSpan.class);
7989
7990                if (links.length > 0) {
7991                    links[0].onClick(this);
7992                    handled = true;
7993                }
7994            }
7995
7996            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
7997                // Show the IME, except when selecting in read-only text.
7998                final InputMethodManager imm = InputMethodManager.peekInstance();
7999                viewClicked(imm);
8000                if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8001                    handled |= imm != null && imm.showSoftInput(this, 0);
8002                }
8003
8004                // The above condition ensures that the mEditor is not null
8005                mEditor.onTouchUpEvent(event);
8006
8007                handled = true;
8008            }
8009
8010            if (handled) {
8011                return true;
8012            }
8013        }
8014
8015        return superResult;
8016    }
8017
8018    @Override
8019    public boolean onGenericMotionEvent(MotionEvent event) {
8020        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8021            try {
8022                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8023                    return true;
8024                }
8025            } catch (AbstractMethodError ex) {
8026                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8027                // Ignore its absence in case third party applications implemented the
8028                // interface directly.
8029            }
8030        }
8031        return super.onGenericMotionEvent(event);
8032    }
8033
8034    /**
8035     * @return True iff this TextView contains a text that can be edited, or if this is
8036     * a selectable TextView.
8037     */
8038    boolean isTextEditable() {
8039        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8040    }
8041
8042    /**
8043     * Returns true, only while processing a touch gesture, if the initial
8044     * touch down event caused focus to move to the text view and as a result
8045     * its selection changed.  Only valid while processing the touch gesture
8046     * of interest, in an editable text view.
8047     */
8048    public boolean didTouchFocusSelect() {
8049        return mEditor != null && mEditor.mTouchFocusSelected;
8050    }
8051
8052    @Override
8053    public void cancelLongPress() {
8054        super.cancelLongPress();
8055        if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8056    }
8057
8058    @Override
8059    public boolean onTrackballEvent(MotionEvent event) {
8060        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8061            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8062                return true;
8063            }
8064        }
8065
8066        return super.onTrackballEvent(event);
8067    }
8068
8069    public void setScroller(Scroller s) {
8070        mScroller = s;
8071    }
8072
8073    @Override
8074    protected float getLeftFadingEdgeStrength() {
8075        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8076                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8077            if (mMarquee != null && !mMarquee.isStopped()) {
8078                final Marquee marquee = mMarquee;
8079                if (marquee.shouldDrawLeftFade()) {
8080                    final float scroll = marquee.getScroll();
8081                    return scroll / getHorizontalFadingEdgeLength();
8082                } else {
8083                    return 0.0f;
8084                }
8085            } else if (getLineCount() == 1) {
8086                final int layoutDirection = getLayoutDirection();
8087                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8088                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8089                    case Gravity.LEFT:
8090                        return 0.0f;
8091                    case Gravity.RIGHT:
8092                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
8093                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
8094                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8095                    case Gravity.CENTER_HORIZONTAL:
8096                    case Gravity.FILL_HORIZONTAL:
8097                        final int textDirection = mLayout.getParagraphDirection(0);
8098                        if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8099                            return 0.0f;
8100                        } else {
8101                            return (mLayout.getLineRight(0) - (mRight - mLeft) -
8102                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
8103                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8104                        }
8105                }
8106            }
8107        }
8108        return super.getLeftFadingEdgeStrength();
8109    }
8110
8111    @Override
8112    protected float getRightFadingEdgeStrength() {
8113        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8114                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8115            if (mMarquee != null && !mMarquee.isStopped()) {
8116                final Marquee marquee = mMarquee;
8117                final float maxFadeScroll = marquee.getMaxFadeScroll();
8118                final float scroll = marquee.getScroll();
8119                return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8120            } else if (getLineCount() == 1) {
8121                final int layoutDirection = getLayoutDirection();
8122                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8123                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8124                    case Gravity.LEFT:
8125                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8126                                getCompoundPaddingRight();
8127                        final float lineWidth = mLayout.getLineWidth(0);
8128                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8129                    case Gravity.RIGHT:
8130                        return 0.0f;
8131                    case Gravity.CENTER_HORIZONTAL:
8132                    case Gravity.FILL_HORIZONTAL:
8133                        final int textDirection = mLayout.getParagraphDirection(0);
8134                        if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8135                            return 0.0f;
8136                        } else {
8137                            return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8138                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8139                                getHorizontalFadingEdgeLength();
8140                        }
8141                }
8142            }
8143        }
8144        return super.getRightFadingEdgeStrength();
8145    }
8146
8147    @Override
8148    protected int computeHorizontalScrollRange() {
8149        if (mLayout != null) {
8150            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8151                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8152        }
8153
8154        return super.computeHorizontalScrollRange();
8155    }
8156
8157    @Override
8158    protected int computeVerticalScrollRange() {
8159        if (mLayout != null)
8160            return mLayout.getHeight();
8161
8162        return super.computeVerticalScrollRange();
8163    }
8164
8165    @Override
8166    protected int computeVerticalScrollExtent() {
8167        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8168    }
8169
8170    @Override
8171    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8172        super.findViewsWithText(outViews, searched, flags);
8173        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8174                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8175            String searchedLowerCase = searched.toString().toLowerCase();
8176            String textLowerCase = mText.toString().toLowerCase();
8177            if (textLowerCase.contains(searchedLowerCase)) {
8178                outViews.add(this);
8179            }
8180        }
8181    }
8182
8183    public enum BufferType {
8184        NORMAL, SPANNABLE, EDITABLE,
8185    }
8186
8187    /**
8188     * Returns the TextView_textColor attribute from the TypedArray, if set, or
8189     * the TextAppearance_textColor from the TextView_textAppearance attribute,
8190     * if TextView_textColor was not set directly.
8191     *
8192     * @removed
8193     */
8194    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8195        if (attrs == null) {
8196            // Preserve behavior prior to removal of this API.
8197            throw new NullPointerException();
8198        }
8199
8200        // It's not safe to use this method from apps. The parameter 'attrs'
8201        // must have been obtained using the TextView filter array which is not
8202        // available to the SDK. As such, we grab a default TypedArray with the
8203        // right filter instead here.
8204        final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8205        ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8206        if (colors == null) {
8207            final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8208            if (ap != 0) {
8209                final TypedArray appearance = context.obtainStyledAttributes(
8210                        ap, R.styleable.TextAppearance);
8211                colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8212                appearance.recycle();
8213            }
8214        }
8215        a.recycle();
8216
8217        return colors;
8218    }
8219
8220    /**
8221     * Returns the default color from the TextView_textColor attribute from the
8222     * AttributeSet, if set, or the default color from the
8223     * TextAppearance_textColor from the TextView_textAppearance attribute, if
8224     * TextView_textColor was not set directly.
8225     *
8226     * @removed
8227     */
8228    public static int getTextColor(Context context, TypedArray attrs, int def) {
8229        final ColorStateList colors = getTextColors(context, attrs);
8230        if (colors == null) {
8231            return def;
8232        } else {
8233            return colors.getDefaultColor();
8234        }
8235    }
8236
8237    @Override
8238    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8239        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
8240        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
8241            switch (keyCode) {
8242            case KeyEvent.KEYCODE_A:
8243                if (canSelectText()) {
8244                    return onTextContextMenuItem(ID_SELECT_ALL);
8245                }
8246                break;
8247            case KeyEvent.KEYCODE_X:
8248                if (canCut()) {
8249                    return onTextContextMenuItem(ID_CUT);
8250                }
8251                break;
8252            case KeyEvent.KEYCODE_C:
8253                if (canCopy()) {
8254                    return onTextContextMenuItem(ID_COPY);
8255                }
8256                break;
8257            case KeyEvent.KEYCODE_V:
8258                if (canPaste()) {
8259                    return onTextContextMenuItem(ID_PASTE);
8260                }
8261                break;
8262            }
8263        }
8264        return super.onKeyShortcut(keyCode, event);
8265    }
8266
8267    /**
8268     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8269     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8270     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8271     * sufficient.
8272     */
8273    private boolean canSelectText() {
8274        return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8275    }
8276
8277    /**
8278     * Test based on the <i>intrinsic</i> charateristics of the TextView.
8279     * The text must be spannable and the movement method must allow for arbitary selection.
8280     *
8281     * See also {@link #canSelectText()}.
8282     */
8283    boolean textCanBeSelected() {
8284        // prepareCursorController() relies on this method.
8285        // If you change this condition, make sure prepareCursorController is called anywhere
8286        // the value of this condition might be changed.
8287        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8288        return isTextEditable() ||
8289                (isTextSelectable() && mText instanceof Spannable && isEnabled());
8290    }
8291
8292    private Locale getTextServicesLocale(boolean allowNullLocale) {
8293        // Start fetching the text services locale asynchronously.
8294        updateTextServicesLocaleAsync();
8295        // If !allowNullLocale and there is no cached text services locale, just return the default
8296        // locale.
8297        return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8298                : mCurrentSpellCheckerLocaleCache;
8299    }
8300
8301    /**
8302     * This is a temporary method. Future versions may support multi-locale text.
8303     * Caveat: This method may not return the latest text services locale, but this should be
8304     * acceptable and it's more important to make this method asynchronous.
8305     *
8306     * @return The locale that should be used for a word iterator
8307     * in this TextView, based on the current spell checker settings,
8308     * the current IME's locale, or the system default locale.
8309     * Please note that a word iterator in this TextView is different from another word iterator
8310     * used by SpellChecker.java of TextView. This method should be used for the former.
8311     * @hide
8312     */
8313    // TODO: Support multi-locale
8314    // TODO: Update the text services locale immediately after the keyboard locale is switched
8315    // by catching intent of keyboard switch event
8316    public Locale getTextServicesLocale() {
8317        return getTextServicesLocale(false /* allowNullLocale */);
8318    }
8319
8320    /**
8321     * This is a temporary method. Future versions may support multi-locale text.
8322     * Caveat: This method may not return the latest spell checker locale, but this should be
8323     * acceptable and it's more important to make this method asynchronous.
8324     *
8325     * @return The locale that should be used for a spell checker in this TextView,
8326     * based on the current spell checker settings, the current IME's locale, or the system default
8327     * locale.
8328     * @hide
8329     */
8330    public Locale getSpellCheckerLocale() {
8331        return getTextServicesLocale(true /* allowNullLocale */);
8332    }
8333
8334    private void updateTextServicesLocaleAsync() {
8335        // AsyncTask.execute() uses a serial executor which means we don't have
8336        // to lock around updateTextServicesLocaleLocked() to prevent it from
8337        // being executed n times in parallel.
8338        AsyncTask.execute(new Runnable() {
8339            @Override
8340            public void run() {
8341                updateTextServicesLocaleLocked();
8342            }
8343        });
8344    }
8345
8346    private void updateTextServicesLocaleLocked() {
8347        final TextServicesManager textServicesManager = (TextServicesManager)
8348                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8349        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8350        final Locale locale;
8351        if (subtype != null) {
8352            locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8353        } else {
8354            locale = null;
8355        }
8356        mCurrentSpellCheckerLocaleCache = locale;
8357    }
8358
8359    void onLocaleChanged() {
8360        // Will be re-created on demand in getWordIterator with the proper new locale
8361        mEditor.mWordIterator = null;
8362    }
8363
8364    /**
8365     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8366     * Made available to achieve a consistent behavior.
8367     * @hide
8368     */
8369    public WordIterator getWordIterator() {
8370        if (mEditor != null) {
8371            return mEditor.getWordIterator();
8372        } else {
8373            return null;
8374        }
8375    }
8376
8377    @Override
8378    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
8379        super.onPopulateAccessibilityEvent(event);
8380
8381        final boolean isPassword = hasPasswordTransformationMethod();
8382        if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8383            final CharSequence text = getTextForAccessibility();
8384            if (!TextUtils.isEmpty(text)) {
8385                event.getText().add(text);
8386            }
8387        }
8388    }
8389
8390    /**
8391     * @return true if the user has explicitly allowed accessibility services
8392     * to speak passwords.
8393     */
8394    private boolean shouldSpeakPasswordsForAccessibility() {
8395        return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8396                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8397                UserHandle.USER_CURRENT_OR_SELF) == 1);
8398    }
8399
8400    @Override
8401    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8402        super.onInitializeAccessibilityEvent(event);
8403
8404        event.setClassName(TextView.class.getName());
8405        final boolean isPassword = hasPasswordTransformationMethod();
8406        event.setPassword(isPassword);
8407
8408        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8409            event.setFromIndex(Selection.getSelectionStart(mText));
8410            event.setToIndex(Selection.getSelectionEnd(mText));
8411            event.setItemCount(mText.length());
8412        }
8413    }
8414
8415    @Override
8416    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8417        super.onInitializeAccessibilityNodeInfo(info);
8418
8419        info.setClassName(TextView.class.getName());
8420        final boolean isPassword = hasPasswordTransformationMethod();
8421        info.setPassword(isPassword);
8422
8423        if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8424            info.setText(getTextForAccessibility());
8425        }
8426
8427        if (mBufferType == BufferType.EDITABLE) {
8428            info.setEditable(true);
8429        }
8430
8431        if (mEditor != null) {
8432            info.setInputType(mEditor.mInputType);
8433
8434            if (mEditor.mError != null) {
8435                info.setContentInvalid(true);
8436                info.setError(mEditor.mError);
8437            }
8438        }
8439
8440        if (!TextUtils.isEmpty(mText)) {
8441            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8442            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8443            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8444                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8445                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8446                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8447                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8448        }
8449
8450        if (isFocused()) {
8451            if (canSelectText()) {
8452                info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8453            }
8454            if (canCopy()) {
8455                info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8456            }
8457            if (canPaste()) {
8458                info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8459            }
8460            if (canCut()) {
8461                info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8462            }
8463        }
8464
8465        // Check for known input filter types.
8466        final int numFilters = mFilters.length;
8467        for (int i = 0; i < numFilters; i++) {
8468            final InputFilter filter = mFilters[i];
8469            if (filter instanceof InputFilter.LengthFilter) {
8470                info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
8471            }
8472        }
8473
8474        if (!isSingleLine()) {
8475            info.setMultiLine(true);
8476        }
8477    }
8478
8479    /**
8480     * Performs an accessibility action after it has been offered to the
8481     * delegate.
8482     *
8483     * @hide
8484     */
8485    @Override
8486    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
8487        switch (action) {
8488            case AccessibilityNodeInfo.ACTION_CLICK: {
8489                boolean handled = false;
8490
8491                // Simulate View.onTouchEvent for an ACTION_UP event.
8492                if (isClickable() || isLongClickable()) {
8493                    if (isFocusable() && !isFocused()) {
8494                        requestFocus();
8495                    }
8496
8497                    performClick();
8498                    handled = true;
8499                }
8500
8501                // Simulate TextView.onTouchEvent for an ACTION_UP event.
8502                if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8503                        && mText instanceof Spannable && mLayout != null
8504                        && (isTextEditable() || isTextSelectable()) && isFocused()) {
8505                    // Show the IME, except when selecting in read-only text.
8506                    final InputMethodManager imm = InputMethodManager.peekInstance();
8507                    viewClicked(imm);
8508                    if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
8509                        handled |= imm.showSoftInput(this, 0);
8510                    }
8511                }
8512
8513                return handled;
8514            }
8515            case AccessibilityNodeInfo.ACTION_COPY: {
8516                if (isFocused() && canCopy()) {
8517                    if (onTextContextMenuItem(ID_COPY)) {
8518                        return true;
8519                    }
8520                }
8521            } return false;
8522            case AccessibilityNodeInfo.ACTION_PASTE: {
8523                if (isFocused() && canPaste()) {
8524                    if (onTextContextMenuItem(ID_PASTE)) {
8525                        return true;
8526                    }
8527                }
8528            } return false;
8529            case AccessibilityNodeInfo.ACTION_CUT: {
8530                if (isFocused() && canCut()) {
8531                    if (onTextContextMenuItem(ID_CUT)) {
8532                        return true;
8533                    }
8534                }
8535            } return false;
8536            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8537                if (isFocused() && canSelectText()) {
8538                    ensureIterableTextForAccessibilitySelectable();
8539                    CharSequence text = getIterableTextForAccessibility();
8540                    if (text == null) {
8541                        return false;
8542                    }
8543                    final int start = (arguments != null) ? arguments.getInt(
8544                            AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8545                    final int end = (arguments != null) ? arguments.getInt(
8546                            AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8547                    if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8548                        // No arguments clears the selection.
8549                        if (start == end && end == -1) {
8550                            Selection.removeSelection((Spannable) text);
8551                            return true;
8552                        }
8553                        if (start >= 0 && start <= end && end <= text.length()) {
8554                            Selection.setSelection((Spannable) text, start, end);
8555                            // Make sure selection mode is engaged.
8556                            if (mEditor != null) {
8557                                mEditor.startSelectionActionMode();
8558                            }
8559                            return true;
8560                        }
8561                    }
8562                }
8563            } return false;
8564            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
8565            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
8566                ensureIterableTextForAccessibilitySelectable();
8567                return super.performAccessibilityActionInternal(action, arguments);
8568            }
8569            default: {
8570                return super.performAccessibilityActionInternal(action, arguments);
8571            }
8572        }
8573    }
8574
8575    @Override
8576    public void sendAccessibilityEvent(int eventType) {
8577        // Do not send scroll events since first they are not interesting for
8578        // accessibility and second such events a generated too frequently.
8579        // For details see the implementation of bringTextIntoView().
8580        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8581            return;
8582        }
8583        super.sendAccessibilityEvent(eventType);
8584    }
8585
8586    /**
8587     * Gets the text reported for accessibility purposes.
8588     *
8589     * @return The accessibility text.
8590     *
8591     * @hide
8592     */
8593    public CharSequence getTextForAccessibility() {
8594        CharSequence text = getText();
8595        if (TextUtils.isEmpty(text)) {
8596            text = getHint();
8597        }
8598        return text;
8599    }
8600
8601    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8602            int fromIndex, int removedCount, int addedCount) {
8603        AccessibilityEvent event =
8604            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8605        event.setFromIndex(fromIndex);
8606        event.setRemovedCount(removedCount);
8607        event.setAddedCount(addedCount);
8608        event.setBeforeText(beforeText);
8609        sendAccessibilityEventUnchecked(event);
8610    }
8611
8612    /**
8613     * Returns whether this text view is a current input method target.  The
8614     * default implementation just checks with {@link InputMethodManager}.
8615     */
8616    public boolean isInputMethodTarget() {
8617        InputMethodManager imm = InputMethodManager.peekInstance();
8618        return imm != null && imm.isActive(this);
8619    }
8620
8621    static final int ID_SELECT_ALL = android.R.id.selectAll;
8622    static final int ID_CUT = android.R.id.cut;
8623    static final int ID_COPY = android.R.id.copy;
8624    static final int ID_PASTE = android.R.id.paste;
8625
8626    /**
8627     * Called when a context menu option for the text view is selected.  Currently
8628     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8629     * {@link android.R.id#copy} or {@link android.R.id#paste}.
8630     *
8631     * @return true if the context menu item action was performed.
8632     */
8633    public boolean onTextContextMenuItem(int id) {
8634        int min = 0;
8635        int max = mText.length();
8636
8637        if (isFocused()) {
8638            final int selStart = getSelectionStart();
8639            final int selEnd = getSelectionEnd();
8640
8641            min = Math.max(0, Math.min(selStart, selEnd));
8642            max = Math.max(0, Math.max(selStart, selEnd));
8643        }
8644
8645        switch (id) {
8646            case ID_SELECT_ALL:
8647                // This does not enter text selection mode. Text is highlighted, so that it can be
8648                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
8649                selectAllText();
8650                return true;
8651
8652            case ID_PASTE:
8653                paste(min, max);
8654                return true;
8655
8656            case ID_CUT:
8657                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8658                deleteText_internal(min, max);
8659                stopSelectionActionMode();
8660                return true;
8661
8662            case ID_COPY:
8663                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8664                stopSelectionActionMode();
8665                return true;
8666        }
8667        return false;
8668    }
8669
8670    CharSequence getTransformedText(int start, int end) {
8671        return removeSuggestionSpans(mTransformed.subSequence(start, end));
8672    }
8673
8674    @Override
8675    public boolean performLongClick() {
8676        boolean handled = false;
8677
8678        if (super.performLongClick()) {
8679            handled = true;
8680        }
8681
8682        if (mEditor != null) {
8683            handled |= mEditor.performLongClick(handled);
8684        }
8685
8686        if (handled) {
8687            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
8688            if (mEditor != null) mEditor.mDiscardNextActionUp = true;
8689        }
8690
8691        return handled;
8692    }
8693
8694    @Override
8695    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8696        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
8697        if (mEditor != null) {
8698            mEditor.onScrollChanged();
8699        }
8700    }
8701
8702    /**
8703     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8704     * by the IME or by the spell checker as the user types. This is done by adding
8705     * {@link SuggestionSpan}s to the text.
8706     *
8707     * When suggestions are enabled (default), this list of suggestions will be displayed when the
8708     * user asks for them on these parts of the text. This value depends on the inputType of this
8709     * TextView.
8710     *
8711     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8712     *
8713     * In addition, the type variation must be one of
8714     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8715     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8716     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8717     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8718     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8719     *
8720     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8721     *
8722     * @return true if the suggestions popup window is enabled, based on the inputType.
8723     */
8724    public boolean isSuggestionsEnabled() {
8725        if (mEditor == null) return false;
8726        if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8727            return false;
8728        }
8729        if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
8730
8731        final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8732        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8733                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8734                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8735                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8736                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8737    }
8738
8739    /**
8740     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8741     * selection is initiated in this View.
8742     *
8743     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8744     * Paste actions, depending on what this View supports.
8745     *
8746     * A custom implementation can add new entries in the default menu in its
8747     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8748     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8749     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8750     * or {@link android.R.id#paste} ids as parameters.
8751     *
8752     * Returning false from
8753     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8754     * the action mode from being started.
8755     *
8756     * Action click events should be handled by the custom implementation of
8757     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8758     *
8759     * Note that text selection mode is not started when a TextView receives focus and the
8760     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8761     * that case, to allow for quick replacement.
8762     */
8763    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8764        createEditorIfNeeded();
8765        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
8766    }
8767
8768    /**
8769     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8770     *
8771     * @return The current custom selection callback.
8772     */
8773    public ActionMode.Callback getCustomSelectionActionModeCallback() {
8774        return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
8775    }
8776
8777    /**
8778     * @hide
8779     */
8780    protected void stopSelectionActionMode() {
8781        mEditor.stopSelectionActionMode();
8782    }
8783
8784    boolean canCut() {
8785        if (hasPasswordTransformationMethod()) {
8786            return false;
8787        }
8788
8789        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8790                mEditor.mKeyListener != null) {
8791            return true;
8792        }
8793
8794        return false;
8795    }
8796
8797    boolean canCopy() {
8798        if (hasPasswordTransformationMethod()) {
8799            return false;
8800        }
8801
8802        if (mText.length() > 0 && hasSelection() && mEditor != null) {
8803            return true;
8804        }
8805
8806        return false;
8807    }
8808
8809    boolean canPaste() {
8810        return (mText instanceof Editable &&
8811                mEditor != null && mEditor.mKeyListener != null &&
8812                getSelectionStart() >= 0 &&
8813                getSelectionEnd() >= 0 &&
8814                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8815                hasPrimaryClip());
8816    }
8817
8818    boolean selectAllText() {
8819        final int length = mText.length();
8820        Selection.setSelection((Spannable) mText, 0, length);
8821        return length > 0;
8822    }
8823
8824    /**
8825     * Paste clipboard content between min and max positions.
8826     */
8827    private void paste(int min, int max) {
8828        ClipboardManager clipboard =
8829            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8830        ClipData clip = clipboard.getPrimaryClip();
8831        if (clip != null) {
8832            boolean didFirst = false;
8833            for (int i=0; i<clip.getItemCount(); i++) {
8834                CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
8835                if (paste != null) {
8836                    if (!didFirst) {
8837                        Selection.setSelection((Spannable) mText, max);
8838                        ((Editable) mText).replace(min, max, paste);
8839                        didFirst = true;
8840                    } else {
8841                        ((Editable) mText).insert(getSelectionEnd(), "\n");
8842                        ((Editable) mText).insert(getSelectionEnd(), paste);
8843                    }
8844                }
8845            }
8846            stopSelectionActionMode();
8847            LAST_CUT_OR_COPY_TIME = 0;
8848        }
8849    }
8850
8851    private void setPrimaryClip(ClipData clip) {
8852        ClipboardManager clipboard = (ClipboardManager) getContext().
8853                getSystemService(Context.CLIPBOARD_SERVICE);
8854        clipboard.setPrimaryClip(clip);
8855        LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8856    }
8857
8858    /**
8859     * Get the character offset closest to the specified absolute position. A typical use case is to
8860     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8861     *
8862     * @param x The horizontal absolute position of a point on screen
8863     * @param y The vertical absolute position of a point on screen
8864     * @return the character offset for the character whose position is closest to the specified
8865     *  position. Returns -1 if there is no layout.
8866     */
8867    public int getOffsetForPosition(float x, float y) {
8868        if (getLayout() == null) return -1;
8869        final int line = getLineAtCoordinate(y);
8870        final int offset = getOffsetAtCoordinate(line, x);
8871        return offset;
8872    }
8873
8874    float convertToLocalHorizontalCoordinate(float x) {
8875        x -= getTotalPaddingLeft();
8876        // Clamp the position to inside of the view.
8877        x = Math.max(0.0f, x);
8878        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8879        x += getScrollX();
8880        return x;
8881    }
8882
8883    int getLineAtCoordinate(float y) {
8884        y -= getTotalPaddingTop();
8885        // Clamp the position to inside of the view.
8886        y = Math.max(0.0f, y);
8887        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8888        y += getScrollY();
8889        return getLayout().getLineForVertical((int) y);
8890    }
8891
8892    private int getOffsetAtCoordinate(int line, float x) {
8893        x = convertToLocalHorizontalCoordinate(x);
8894        return getLayout().getOffsetForHorizontal(line, x);
8895    }
8896
8897    @Override
8898    public boolean onDragEvent(DragEvent event) {
8899        switch (event.getAction()) {
8900            case DragEvent.ACTION_DRAG_STARTED:
8901                return mEditor != null && mEditor.hasInsertionController();
8902
8903            case DragEvent.ACTION_DRAG_ENTERED:
8904                TextView.this.requestFocus();
8905                return true;
8906
8907            case DragEvent.ACTION_DRAG_LOCATION:
8908                final int offset = getOffsetForPosition(event.getX(), event.getY());
8909                Selection.setSelection((Spannable)mText, offset);
8910                return true;
8911
8912            case DragEvent.ACTION_DROP:
8913                if (mEditor != null) mEditor.onDrop(event);
8914                return true;
8915
8916            case DragEvent.ACTION_DRAG_ENDED:
8917            case DragEvent.ACTION_DRAG_EXITED:
8918            default:
8919                return true;
8920        }
8921    }
8922
8923    boolean isInBatchEditMode() {
8924        if (mEditor == null) return false;
8925        final Editor.InputMethodState ims = mEditor.mInputMethodState;
8926        if (ims != null) {
8927            return ims.mBatchEditNesting > 0;
8928        }
8929        return mEditor.mInBatchEditControllers;
8930    }
8931
8932    @Override
8933    public void onRtlPropertiesChanged(int layoutDirection) {
8934        super.onRtlPropertiesChanged(layoutDirection);
8935
8936        mTextDir = getTextDirectionHeuristic();
8937
8938        if (mLayout != null) {
8939            checkForRelayout();
8940        }
8941    }
8942
8943    TextDirectionHeuristic getTextDirectionHeuristic() {
8944        if (hasPasswordTransformationMethod()) {
8945            // passwords fields should be LTR
8946            return TextDirectionHeuristics.LTR;
8947        }
8948
8949        // Always need to resolve layout direction first
8950        final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
8951
8952        // Now, we can select the heuristic
8953        switch (getTextDirection()) {
8954            default:
8955            case TEXT_DIRECTION_FIRST_STRONG:
8956                return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
8957                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
8958            case TEXT_DIRECTION_ANY_RTL:
8959                return TextDirectionHeuristics.ANYRTL_LTR;
8960            case TEXT_DIRECTION_LTR:
8961                return TextDirectionHeuristics.LTR;
8962            case TEXT_DIRECTION_RTL:
8963                return TextDirectionHeuristics.RTL;
8964            case TEXT_DIRECTION_LOCALE:
8965                return TextDirectionHeuristics.LOCALE;
8966        }
8967    }
8968
8969    /**
8970     * @hide
8971     */
8972    @Override
8973    public void onResolveDrawables(int layoutDirection) {
8974        // No need to resolve twice
8975        if (mLastLayoutDirection == layoutDirection) {
8976            return;
8977        }
8978        mLastLayoutDirection = layoutDirection;
8979
8980        // Resolve drawables
8981        if (mDrawables != null) {
8982            mDrawables.resolveWithLayoutDirection(layoutDirection);
8983        }
8984    }
8985
8986    /**
8987     * @hide
8988     */
8989    protected void resetResolvedDrawables() {
8990        super.resetResolvedDrawables();
8991        mLastLayoutDirection = -1;
8992    }
8993
8994    /**
8995     * @hide
8996     */
8997    protected void viewClicked(InputMethodManager imm) {
8998        if (imm != null) {
8999            imm.viewClicked(this);
9000        }
9001    }
9002
9003    /**
9004     * Deletes the range of text [start, end[.
9005     * @hide
9006     */
9007    protected void deleteText_internal(int start, int end) {
9008        ((Editable) mText).delete(start, end);
9009    }
9010
9011    /**
9012     * Replaces the range of text [start, end[ by replacement text
9013     * @hide
9014     */
9015    protected void replaceText_internal(int start, int end, CharSequence text) {
9016        ((Editable) mText).replace(start, end, text);
9017    }
9018
9019    /**
9020     * Sets a span on the specified range of text
9021     * @hide
9022     */
9023    protected void setSpan_internal(Object span, int start, int end, int flags) {
9024        ((Editable) mText).setSpan(span, start, end, flags);
9025    }
9026
9027    /**
9028     * Moves the cursor to the specified offset position in text
9029     * @hide
9030     */
9031    protected void setCursorPosition_internal(int start, int end) {
9032        Selection.setSelection(((Editable) mText), start, end);
9033    }
9034
9035    /**
9036     * An Editor should be created as soon as any of the editable-specific fields (grouped
9037     * inside the Editor object) is assigned to a non-default value.
9038     * This method will create the Editor if needed.
9039     *
9040     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9041     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9042     * Editor for backward compatibility, as soon as one of these fields is assigned.
9043     *
9044     * Also note that for performance reasons, the mEditor is created when needed, but not
9045     * reset when no more edit-specific fields are needed.
9046     */
9047    private void createEditorIfNeeded() {
9048        if (mEditor == null) {
9049            mEditor = new Editor(this);
9050        }
9051    }
9052
9053    /**
9054     * @hide
9055     */
9056    @Override
9057    public CharSequence getIterableTextForAccessibility() {
9058        return mText;
9059    }
9060
9061    private void ensureIterableTextForAccessibilitySelectable() {
9062        if (!(mText instanceof Spannable)) {
9063            setText(mText, BufferType.SPANNABLE);
9064        }
9065    }
9066
9067    /**
9068     * @hide
9069     */
9070    @Override
9071    public TextSegmentIterator getIteratorForGranularity(int granularity) {
9072        switch (granularity) {
9073            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9074                Spannable text = (Spannable) getIterableTextForAccessibility();
9075                if (!TextUtils.isEmpty(text) && getLayout() != null) {
9076                    AccessibilityIterators.LineTextSegmentIterator iterator =
9077                        AccessibilityIterators.LineTextSegmentIterator.getInstance();
9078                    iterator.initialize(text, getLayout());
9079                    return iterator;
9080                }
9081            } break;
9082            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9083                Spannable text = (Spannable) getIterableTextForAccessibility();
9084                if (!TextUtils.isEmpty(text) && getLayout() != null) {
9085                    AccessibilityIterators.PageTextSegmentIterator iterator =
9086                        AccessibilityIterators.PageTextSegmentIterator.getInstance();
9087                    iterator.initialize(this);
9088                    return iterator;
9089                }
9090            } break;
9091        }
9092        return super.getIteratorForGranularity(granularity);
9093    }
9094
9095    /**
9096     * @hide
9097     */
9098    @Override
9099    public int getAccessibilitySelectionStart() {
9100        return getSelectionStart();
9101    }
9102
9103    /**
9104     * @hide
9105     */
9106    public boolean isAccessibilitySelectionExtendable() {
9107        return true;
9108    }
9109
9110    /**
9111     * @hide
9112     */
9113    @Override
9114    public int getAccessibilitySelectionEnd() {
9115        return getSelectionEnd();
9116    }
9117
9118    /**
9119     * @hide
9120     */
9121    @Override
9122    public void setAccessibilitySelection(int start, int end) {
9123        if (getAccessibilitySelectionStart() == start
9124                && getAccessibilitySelectionEnd() == end) {
9125            return;
9126        }
9127        // Hide all selection controllers used for adjusting selection
9128        // since we are doing so explicitlty by other means and these
9129        // controllers interact with how selection behaves.
9130        if (mEditor != null) {
9131            mEditor.hideControllers();
9132        }
9133        CharSequence text = getIterableTextForAccessibility();
9134        if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
9135            Selection.setSelection((Spannable) text, start, end);
9136        } else {
9137            Selection.removeSelection((Spannable) text);
9138        }
9139    }
9140
9141    /**
9142     * User interface state that is stored by TextView for implementing
9143     * {@link View#onSaveInstanceState}.
9144     */
9145    public static class SavedState extends BaseSavedState {
9146        int selStart;
9147        int selEnd;
9148        CharSequence text;
9149        boolean frozenWithFocus;
9150        CharSequence error;
9151
9152        SavedState(Parcelable superState) {
9153            super(superState);
9154        }
9155
9156        @Override
9157        public void writeToParcel(Parcel out, int flags) {
9158            super.writeToParcel(out, flags);
9159            out.writeInt(selStart);
9160            out.writeInt(selEnd);
9161            out.writeInt(frozenWithFocus ? 1 : 0);
9162            TextUtils.writeToParcel(text, out, flags);
9163
9164            if (error == null) {
9165                out.writeInt(0);
9166            } else {
9167                out.writeInt(1);
9168                TextUtils.writeToParcel(error, out, flags);
9169            }
9170        }
9171
9172        @Override
9173        public String toString() {
9174            String str = "TextView.SavedState{"
9175                    + Integer.toHexString(System.identityHashCode(this))
9176                    + " start=" + selStart + " end=" + selEnd;
9177            if (text != null) {
9178                str += " text=" + text;
9179            }
9180            return str + "}";
9181        }
9182
9183        @SuppressWarnings("hiding")
9184        public static final Parcelable.Creator<SavedState> CREATOR
9185                = new Parcelable.Creator<SavedState>() {
9186            public SavedState createFromParcel(Parcel in) {
9187                return new SavedState(in);
9188            }
9189
9190            public SavedState[] newArray(int size) {
9191                return new SavedState[size];
9192            }
9193        };
9194
9195        private SavedState(Parcel in) {
9196            super(in);
9197            selStart = in.readInt();
9198            selEnd = in.readInt();
9199            frozenWithFocus = (in.readInt() != 0);
9200            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9201
9202            if (in.readInt() != 0) {
9203                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9204            }
9205        }
9206    }
9207
9208    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
9209        private char[] mChars;
9210        private int mStart, mLength;
9211
9212        public CharWrapper(char[] chars, int start, int len) {
9213            mChars = chars;
9214            mStart = start;
9215            mLength = len;
9216        }
9217
9218        /* package */ void set(char[] chars, int start, int len) {
9219            mChars = chars;
9220            mStart = start;
9221            mLength = len;
9222        }
9223
9224        public int length() {
9225            return mLength;
9226        }
9227
9228        public char charAt(int off) {
9229            return mChars[off + mStart];
9230        }
9231
9232        @Override
9233        public String toString() {
9234            return new String(mChars, mStart, mLength);
9235        }
9236
9237        public CharSequence subSequence(int start, int end) {
9238            if (start < 0 || end < 0 || start > mLength || end > mLength) {
9239                throw new IndexOutOfBoundsException(start + ", " + end);
9240            }
9241
9242            return new String(mChars, start + mStart, end - start);
9243        }
9244
9245        public void getChars(int start, int end, char[] buf, int off) {
9246            if (start < 0 || end < 0 || start > mLength || end > mLength) {
9247                throw new IndexOutOfBoundsException(start + ", " + end);
9248            }
9249
9250            System.arraycopy(mChars, start + mStart, buf, off, end - start);
9251        }
9252
9253        public void drawText(Canvas c, int start, int end,
9254                             float x, float y, Paint p) {
9255            c.drawText(mChars, start + mStart, end - start, x, y, p);
9256        }
9257
9258        public void drawTextRun(Canvas c, int start, int end,
9259                int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
9260            int count = end - start;
9261            int contextCount = contextEnd - contextStart;
9262            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9263                    contextCount, x, y, isRtl, p);
9264        }
9265
9266        public float measureText(int start, int end, Paint p) {
9267            return p.measureText(mChars, start + mStart, end - start);
9268        }
9269
9270        public int getTextWidths(int start, int end, float[] widths, Paint p) {
9271            return p.getTextWidths(mChars, start + mStart, end - start, widths);
9272        }
9273
9274        public float getTextRunAdvances(int start, int end, int contextStart,
9275                int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
9276                Paint p) {
9277            int count = end - start;
9278            int contextCount = contextEnd - contextStart;
9279            return p.getTextRunAdvances(mChars, start + mStart, count,
9280                    contextStart + mStart, contextCount, isRtl, advances,
9281                    advancesIndex);
9282        }
9283
9284        public int getTextRunCursor(int contextStart, int contextEnd, int dir,
9285                int offset, int cursorOpt, Paint p) {
9286            int contextCount = contextEnd - contextStart;
9287            return p.getTextRunCursor(mChars, contextStart + mStart,
9288                    contextCount, dir, offset + mStart, cursorOpt);
9289        }
9290    }
9291
9292    private static final class Marquee {
9293        // TODO: Add an option to configure this
9294        private static final float MARQUEE_DELTA_MAX = 0.07f;
9295        private static final int MARQUEE_DELAY = 1200;
9296        private static final int MARQUEE_RESTART_DELAY = 1200;
9297        private static final int MARQUEE_DP_PER_SECOND = 30;
9298
9299        private static final byte MARQUEE_STOPPED = 0x0;
9300        private static final byte MARQUEE_STARTING = 0x1;
9301        private static final byte MARQUEE_RUNNING = 0x2;
9302
9303        private final WeakReference<TextView> mView;
9304        private final Choreographer mChoreographer;
9305
9306        private byte mStatus = MARQUEE_STOPPED;
9307        private final float mPixelsPerSecond;
9308        private float mMaxScroll;
9309        private float mMaxFadeScroll;
9310        private float mGhostStart;
9311        private float mGhostOffset;
9312        private float mFadeStop;
9313        private int mRepeatLimit;
9314
9315        private float mScroll;
9316        private long mLastAnimationMs;
9317
9318        Marquee(TextView v) {
9319            final float density = v.getContext().getResources().getDisplayMetrics().density;
9320            mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
9321            mView = new WeakReference<TextView>(v);
9322            mChoreographer = Choreographer.getInstance();
9323        }
9324
9325        private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
9326            @Override
9327            public void doFrame(long frameTimeNanos) {
9328                tick();
9329            }
9330        };
9331
9332        private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
9333            @Override
9334            public void doFrame(long frameTimeNanos) {
9335                mStatus = MARQUEE_RUNNING;
9336                mLastAnimationMs = mChoreographer.getFrameTime();
9337                tick();
9338            }
9339        };
9340
9341        private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
9342            @Override
9343            public void doFrame(long frameTimeNanos) {
9344                if (mStatus == MARQUEE_RUNNING) {
9345                    if (mRepeatLimit >= 0) {
9346                        mRepeatLimit--;
9347                    }
9348                    start(mRepeatLimit);
9349                }
9350            }
9351        };
9352
9353        void tick() {
9354            if (mStatus != MARQUEE_RUNNING) {
9355                return;
9356            }
9357
9358            mChoreographer.removeFrameCallback(mTickCallback);
9359
9360            final TextView textView = mView.get();
9361            if (textView != null && (textView.isFocused() || textView.isSelected())) {
9362                long currentMs = mChoreographer.getFrameTime();
9363                long deltaMs = currentMs - mLastAnimationMs;
9364                mLastAnimationMs = currentMs;
9365                float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
9366                mScroll += deltaPx;
9367                if (mScroll > mMaxScroll) {
9368                    mScroll = mMaxScroll;
9369                    mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
9370                } else {
9371                    mChoreographer.postFrameCallback(mTickCallback);
9372                }
9373                textView.invalidate();
9374            }
9375        }
9376
9377        void stop() {
9378            mStatus = MARQUEE_STOPPED;
9379            mChoreographer.removeFrameCallback(mStartCallback);
9380            mChoreographer.removeFrameCallback(mRestartCallback);
9381            mChoreographer.removeFrameCallback(mTickCallback);
9382            resetScroll();
9383        }
9384
9385        private void resetScroll() {
9386            mScroll = 0.0f;
9387            final TextView textView = mView.get();
9388            if (textView != null) textView.invalidate();
9389        }
9390
9391        void start(int repeatLimit) {
9392            if (repeatLimit == 0) {
9393                stop();
9394                return;
9395            }
9396            mRepeatLimit = repeatLimit;
9397            final TextView textView = mView.get();
9398            if (textView != null && textView.mLayout != null) {
9399                mStatus = MARQUEE_STARTING;
9400                mScroll = 0.0f;
9401                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9402                        textView.getCompoundPaddingRight();
9403                final float lineWidth = textView.mLayout.getLineWidth(0);
9404                final float gap = textWidth / 3.0f;
9405                mGhostStart = lineWidth - textWidth + gap;
9406                mMaxScroll = mGhostStart + textWidth;
9407                mGhostOffset = lineWidth + gap;
9408                mFadeStop = lineWidth + textWidth / 6.0f;
9409                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9410
9411                textView.invalidate();
9412                mChoreographer.postFrameCallback(mStartCallback);
9413            }
9414        }
9415
9416        float getGhostOffset() {
9417            return mGhostOffset;
9418        }
9419
9420        float getScroll() {
9421            return mScroll;
9422        }
9423
9424        float getMaxFadeScroll() {
9425            return mMaxFadeScroll;
9426        }
9427
9428        boolean shouldDrawLeftFade() {
9429            return mScroll <= mFadeStop;
9430        }
9431
9432        boolean shouldDrawGhost() {
9433            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9434        }
9435
9436        boolean isRunning() {
9437            return mStatus == MARQUEE_RUNNING;
9438        }
9439
9440        boolean isStopped() {
9441            return mStatus == MARQUEE_STOPPED;
9442        }
9443    }
9444
9445    private class ChangeWatcher implements TextWatcher, SpanWatcher {
9446
9447        private CharSequence mBeforeText;
9448
9449        public void beforeTextChanged(CharSequence buffer, int start,
9450                                      int before, int after) {
9451            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9452                    + " before=" + before + " after=" + after + ": " + buffer);
9453
9454            if (AccessibilityManager.getInstance(mContext).isEnabled()
9455                    && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9456                            || shouldSpeakPasswordsForAccessibility())) {
9457                mBeforeText = buffer.toString();
9458            }
9459
9460            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9461        }
9462
9463        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
9464            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9465                    + " before=" + before + " after=" + after + ": " + buffer);
9466            TextView.this.handleTextChanged(buffer, start, before, after);
9467
9468            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9469                    (isFocused() || isSelected() && isShown())) {
9470                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9471                mBeforeText = null;
9472            }
9473        }
9474
9475        public void afterTextChanged(Editable buffer) {
9476            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9477            TextView.this.sendAfterTextChanged(buffer);
9478
9479            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9480                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9481            }
9482        }
9483
9484        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
9485            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9486                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9487            TextView.this.spanChange(buf, what, s, st, e, en);
9488        }
9489
9490        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9491            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9492                    + " what=" + what + ": " + buf);
9493            TextView.this.spanChange(buf, what, -1, s, -1, e);
9494        }
9495
9496        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9497            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9498                    + " what=" + what + ": " + buf);
9499            TextView.this.spanChange(buf, what, s, -1, e, -1);
9500        }
9501    }
9502}
9503