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