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