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