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