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