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