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