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