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