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