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