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