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