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