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