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