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