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