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