TextView.java revision a4371bc564f7ae307a9b901ea739e615ab235d6a
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        int linecount = layout.getLineCount();
7744        final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
7745        desired += padding;
7746
7747        if (mMaxMode != LINES) {
7748            desired = Math.min(desired, mMaximum);
7749        } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) {
7750            desired = layout.getLineTop(mMaximum);
7751
7752            if (dr != null) {
7753                desired = Math.max(desired, dr.mDrawableHeightLeft);
7754                desired = Math.max(desired, dr.mDrawableHeightRight);
7755            }
7756
7757            desired += padding;
7758            linecount = mMaximum;
7759        }
7760
7761        if (mMinMode == LINES) {
7762            if (linecount < mMinimum) {
7763                desired += getLineHeight() * (mMinimum - linecount);
7764            }
7765        } else {
7766            desired = Math.max(desired, mMinimum);
7767        }
7768
7769        // Check against our minimum height
7770        desired = Math.max(desired, getSuggestedMinimumHeight());
7771
7772        return desired;
7773    }
7774
7775    /**
7776     * Check whether a change to the existing text layout requires a
7777     * new view layout.
7778     */
7779    private void checkForResize() {
7780        boolean sizeChanged = false;
7781
7782        if (mLayout != null) {
7783            // Check if our width changed
7784            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7785                sizeChanged = true;
7786                invalidate();
7787            }
7788
7789            // Check if our height changed
7790            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7791                int desiredHeight = getDesiredHeight();
7792
7793                if (desiredHeight != this.getHeight()) {
7794                    sizeChanged = true;
7795                }
7796            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7797                if (mDesiredHeightAtMeasure >= 0) {
7798                    int desiredHeight = getDesiredHeight();
7799
7800                    if (desiredHeight != mDesiredHeightAtMeasure) {
7801                        sizeChanged = true;
7802                    }
7803                }
7804            }
7805        }
7806
7807        if (sizeChanged) {
7808            requestLayout();
7809            // caller will have already invalidated
7810        }
7811    }
7812
7813    /**
7814     * Check whether entirely new text requires a new view layout
7815     * or merely a new text layout.
7816     */
7817    private void checkForRelayout() {
7818        // If we have a fixed width, we can just swap in a new text layout
7819        // if the text height stays the same or if the view height is fixed.
7820
7821        if (((mLayoutParams.width != LayoutParams.WRAP_CONTENT && mLayoutParams.width != 0)
7822                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
7823                && (mHint == null || mHintLayout != null)
7824                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7825            // Static width, so try making a new text layout.
7826
7827            int oldht = mLayout.getHeight();
7828            int want = mLayout.getWidth();
7829            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7830
7831            /*
7832             * No need to bring the text into view, since the size is not
7833             * changing (unless we do the requestLayout(), in which case it
7834             * will happen at measure).
7835             */
7836            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7837                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7838                          false);
7839
7840            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7841                // In a fixed-height view, so use our new text layout.
7842                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
7843                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7844                    invalidate();
7845                    return;
7846                }
7847
7848                // Dynamic height, but height has stayed the same,
7849                // so use our new text layout.
7850                if (mLayout.getHeight() == oldht
7851                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7852                    invalidate();
7853                    return;
7854                }
7855            }
7856
7857            // We lose: the height has changed and we have a dynamic height.
7858            // Request a new view layout using our new text layout.
7859            requestLayout();
7860            invalidate();
7861        } else {
7862            // Dynamic width, so we have no choice but to request a new
7863            // view layout with a new text layout.
7864            nullLayouts();
7865            requestLayout();
7866            invalidate();
7867        }
7868    }
7869
7870    @Override
7871    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7872        super.onLayout(changed, left, top, right, bottom);
7873        if (mDeferScroll >= 0) {
7874            int curs = mDeferScroll;
7875            mDeferScroll = -1;
7876            bringPointIntoView(Math.min(curs, mText.length()));
7877        }
7878    }
7879
7880    private boolean isShowingHint() {
7881        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7882    }
7883
7884    /**
7885     * Returns true if anything changed.
7886     */
7887    private boolean bringTextIntoView() {
7888        Layout layout = isShowingHint() ? mHintLayout : mLayout;
7889        int line = 0;
7890        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7891            line = layout.getLineCount() - 1;
7892        }
7893
7894        Layout.Alignment a = layout.getParagraphAlignment(line);
7895        int dir = layout.getParagraphDirection(line);
7896        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7897        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7898        int ht = layout.getHeight();
7899
7900        int scrollx, scrolly;
7901
7902        // Convert to left, center, or right alignment.
7903        if (a == Layout.Alignment.ALIGN_NORMAL) {
7904            a = dir == Layout.DIR_LEFT_TO_RIGHT
7905                    ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
7906        } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
7907            a = dir == Layout.DIR_LEFT_TO_RIGHT
7908                    ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
7909        }
7910
7911        if (a == Layout.Alignment.ALIGN_CENTER) {
7912            /*
7913             * Keep centered if possible, or, if it is too wide to fit,
7914             * keep leading edge in view.
7915             */
7916
7917            int left = (int) Math.floor(layout.getLineLeft(line));
7918            int right = (int) Math.ceil(layout.getLineRight(line));
7919
7920            if (right - left < hspace) {
7921                scrollx = (right + left) / 2 - hspace / 2;
7922            } else {
7923                if (dir < 0) {
7924                    scrollx = right - hspace;
7925                } else {
7926                    scrollx = left;
7927                }
7928            }
7929        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7930            int right = (int) Math.ceil(layout.getLineRight(line));
7931            scrollx = right - hspace;
7932        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7933            scrollx = (int) Math.floor(layout.getLineLeft(line));
7934        }
7935
7936        if (ht < vspace) {
7937            scrolly = 0;
7938        } else {
7939            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7940                scrolly = ht - vspace;
7941            } else {
7942                scrolly = 0;
7943            }
7944        }
7945
7946        if (scrollx != mScrollX || scrolly != mScrollY) {
7947            scrollTo(scrollx, scrolly);
7948            return true;
7949        } else {
7950            return false;
7951        }
7952    }
7953
7954    /**
7955     * Move the point, specified by the offset, into the view if it is needed.
7956     * This has to be called after layout. Returns true if anything changed.
7957     */
7958    public boolean bringPointIntoView(int offset) {
7959        if (isLayoutRequested()) {
7960            mDeferScroll = offset;
7961            return false;
7962        }
7963        boolean changed = false;
7964
7965        Layout layout = isShowingHint() ? mHintLayout : mLayout;
7966
7967        if (layout == null) return changed;
7968
7969        int line = layout.getLineForOffset(offset);
7970
7971        int grav;
7972
7973        switch (layout.getParagraphAlignment(line)) {
7974            case ALIGN_LEFT:
7975                grav = 1;
7976                break;
7977            case ALIGN_RIGHT:
7978                grav = -1;
7979                break;
7980            case ALIGN_NORMAL:
7981                grav = layout.getParagraphDirection(line);
7982                break;
7983            case ALIGN_OPPOSITE:
7984                grav = -layout.getParagraphDirection(line);
7985                break;
7986            case ALIGN_CENTER:
7987            default:
7988                grav = 0;
7989                break;
7990        }
7991
7992        // We only want to clamp the cursor to fit within the layout width
7993        // in left-to-right modes, because in a right to left alignment,
7994        // we want to scroll to keep the line-right on the screen, as other
7995        // lines are likely to have text flush with the right margin, which
7996        // we want to keep visible.
7997        // A better long-term solution would probably be to measure both
7998        // the full line and a blank-trimmed version, and, for example, use
7999        // the latter measurement for centering and right alignment, but for
8000        // the time being we only implement the cursor clamping in left to
8001        // right where it is most likely to be annoying.
8002        final boolean clamped = grav > 0;
8003        // FIXME: Is it okay to truncate this, or should we round?
8004        final int x = (int) layout.getPrimaryHorizontal(offset, clamped, true);
8005        final int top = layout.getLineTop(line);
8006        final int bottom = layout.getLineTop(line + 1);
8007
8008        int left = (int) Math.floor(layout.getLineLeft(line));
8009        int right = (int) Math.ceil(layout.getLineRight(line));
8010        int ht = layout.getHeight();
8011
8012        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8013        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8014        if (!mHorizontallyScrolling && right - left > hspace && right > x) {
8015            // If cursor has been clamped, make sure we don't scroll.
8016            right = Math.max(x, left + hspace);
8017        }
8018
8019        int hslack = (bottom - top) / 2;
8020        int vslack = hslack;
8021
8022        if (vslack > vspace / 4) {
8023            vslack = vspace / 4;
8024        }
8025        if (hslack > hspace / 4) {
8026            hslack = hspace / 4;
8027        }
8028
8029        int hs = mScrollX;
8030        int vs = mScrollY;
8031
8032        if (top - vs < vslack) {
8033            vs = top - vslack;
8034        }
8035        if (bottom - vs > vspace - vslack) {
8036            vs = bottom - (vspace - vslack);
8037        }
8038        if (ht - vs < vspace) {
8039            vs = ht - vspace;
8040        }
8041        if (0 - vs > 0) {
8042            vs = 0;
8043        }
8044
8045        if (grav != 0) {
8046            if (x - hs < hslack) {
8047                hs = x - hslack;
8048            }
8049            if (x - hs > hspace - hslack) {
8050                hs = x - (hspace - hslack);
8051            }
8052        }
8053
8054        if (grav < 0) {
8055            if (left - hs > 0) {
8056                hs = left;
8057            }
8058            if (right - hs < hspace) {
8059                hs = right - hspace;
8060            }
8061        } else if (grav > 0) {
8062            if (right - hs < hspace) {
8063                hs = right - hspace;
8064            }
8065            if (left - hs > 0) {
8066                hs = left;
8067            }
8068        } else /* grav == 0 */ {
8069            if (right - left <= hspace) {
8070                /*
8071                 * If the entire text fits, center it exactly.
8072                 */
8073                hs = left - (hspace - (right - left)) / 2;
8074            } else if (x > right - hslack) {
8075                /*
8076                 * If we are near the right edge, keep the right edge
8077                 * at the edge of the view.
8078                 */
8079                hs = right - hspace;
8080            } else if (x < left + hslack) {
8081                /*
8082                 * If we are near the left edge, keep the left edge
8083                 * at the edge of the view.
8084                 */
8085                hs = left;
8086            } else if (left > hs) {
8087                /*
8088                 * Is there whitespace visible at the left?  Fix it if so.
8089                 */
8090                hs = left;
8091            } else if (right < hs + hspace) {
8092                /*
8093                 * Is there whitespace visible at the right?  Fix it if so.
8094                 */
8095                hs = right - hspace;
8096            } else {
8097                /*
8098                 * Otherwise, float as needed.
8099                 */
8100                if (x - hs < hslack) {
8101                    hs = x - hslack;
8102                }
8103                if (x - hs > hspace - hslack) {
8104                    hs = x - (hspace - hslack);
8105                }
8106            }
8107        }
8108
8109        if (hs != mScrollX || vs != mScrollY) {
8110            if (mScroller == null) {
8111                scrollTo(hs, vs);
8112            } else {
8113                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
8114                int dx = hs - mScrollX;
8115                int dy = vs - mScrollY;
8116
8117                if (duration > ANIMATED_SCROLL_GAP) {
8118                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
8119                    awakenScrollBars(mScroller.getDuration());
8120                    invalidate();
8121                } else {
8122                    if (!mScroller.isFinished()) {
8123                        mScroller.abortAnimation();
8124                    }
8125
8126                    scrollBy(dx, dy);
8127                }
8128
8129                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
8130            }
8131
8132            changed = true;
8133        }
8134
8135        if (isFocused()) {
8136            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
8137            // requestRectangleOnScreen() is in terms of content coordinates.
8138
8139            // The offsets here are to ensure the rectangle we are using is
8140            // within our view bounds, in case the cursor is on the far left
8141            // or right.  If it isn't withing the bounds, then this request
8142            // will be ignored.
8143            if (mTempRect == null) mTempRect = new Rect();
8144            mTempRect.set(x - 2, top, x + 2, bottom);
8145            getInterestingRect(mTempRect, line);
8146            mTempRect.offset(mScrollX, mScrollY);
8147
8148            if (requestRectangleOnScreen(mTempRect)) {
8149                changed = true;
8150            }
8151        }
8152
8153        return changed;
8154    }
8155
8156    /**
8157     * Move the cursor, if needed, so that it is at an offset that is visible
8158     * to the user.  This will not move the cursor if it represents more than
8159     * one character (a selection range).  This will only work if the
8160     * TextView contains spannable text; otherwise it will do nothing.
8161     *
8162     * @return True if the cursor was actually moved, false otherwise.
8163     */
8164    public boolean moveCursorToVisibleOffset() {
8165        if (!(mText instanceof Spannable)) {
8166            return false;
8167        }
8168        int start = getSelectionStart();
8169        int end = getSelectionEnd();
8170        if (start != end) {
8171            return false;
8172        }
8173
8174        // First: make sure the line is visible on screen:
8175
8176        int line = mLayout.getLineForOffset(start);
8177
8178        final int top = mLayout.getLineTop(line);
8179        final int bottom = mLayout.getLineTop(line + 1);
8180        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8181        int vslack = (bottom - top) / 2;
8182        if (vslack > vspace / 4) {
8183            vslack = vspace / 4;
8184        }
8185        final int vs = mScrollY;
8186
8187        if (top < (vs + vslack)) {
8188            line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
8189        } else if (bottom > (vspace + vs - vslack)) {
8190            line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
8191        }
8192
8193        // Next: make sure the character is visible on screen:
8194
8195        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8196        final int hs = mScrollX;
8197        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
8198        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
8199
8200        // line might contain bidirectional text
8201        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
8202        final int highChar = leftChar > rightChar ? leftChar : rightChar;
8203
8204        int newStart = start;
8205        if (newStart < lowChar) {
8206            newStart = lowChar;
8207        } else if (newStart > highChar) {
8208            newStart = highChar;
8209        }
8210
8211        if (newStart != start) {
8212            Selection.setSelection((Spannable) mText, newStart);
8213            return true;
8214        }
8215
8216        return false;
8217    }
8218
8219    @Override
8220    public void computeScroll() {
8221        if (mScroller != null) {
8222            if (mScroller.computeScrollOffset()) {
8223                mScrollX = mScroller.getCurrX();
8224                mScrollY = mScroller.getCurrY();
8225                invalidateParentCaches();
8226                postInvalidate();  // So we draw again
8227            }
8228        }
8229    }
8230
8231    private void getInterestingRect(Rect r, int line) {
8232        convertFromViewportToContentCoordinates(r);
8233
8234        // Rectangle can can be expanded on first and last line to take
8235        // padding into account.
8236        // TODO Take left/right padding into account too?
8237        if (line == 0) r.top -= getExtendedPaddingTop();
8238        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
8239    }
8240
8241    private void convertFromViewportToContentCoordinates(Rect r) {
8242        final int horizontalOffset = viewportToContentHorizontalOffset();
8243        r.left += horizontalOffset;
8244        r.right += horizontalOffset;
8245
8246        final int verticalOffset = viewportToContentVerticalOffset();
8247        r.top += verticalOffset;
8248        r.bottom += verticalOffset;
8249    }
8250
8251    int viewportToContentHorizontalOffset() {
8252        return getCompoundPaddingLeft() - mScrollX;
8253    }
8254
8255    int viewportToContentVerticalOffset() {
8256        int offset = getExtendedPaddingTop() - mScrollY;
8257        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8258            offset += getVerticalOffset(false);
8259        }
8260        return offset;
8261    }
8262
8263    @Override
8264    public void debug(int depth) {
8265        super.debug(depth);
8266
8267        String output = debugIndent(depth);
8268        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
8269                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
8270                + "} ";
8271
8272        if (mText != null) {
8273
8274            output += "mText=\"" + mText + "\" ";
8275            if (mLayout != null) {
8276                output += "mLayout width=" + mLayout.getWidth()
8277                        + " height=" + mLayout.getHeight();
8278            }
8279        } else {
8280            output += "mText=NULL";
8281        }
8282        Log.d(VIEW_LOG_TAG, output);
8283    }
8284
8285    /**
8286     * Convenience for {@link Selection#getSelectionStart}.
8287     */
8288    @ViewDebug.ExportedProperty(category = "text")
8289    public int getSelectionStart() {
8290        return Selection.getSelectionStart(getText());
8291    }
8292
8293    /**
8294     * Convenience for {@link Selection#getSelectionEnd}.
8295     */
8296    @ViewDebug.ExportedProperty(category = "text")
8297    public int getSelectionEnd() {
8298        return Selection.getSelectionEnd(getText());
8299    }
8300
8301    /**
8302     * Return true iff there is a selection inside this text view.
8303     */
8304    public boolean hasSelection() {
8305        final int selectionStart = getSelectionStart();
8306        final int selectionEnd = getSelectionEnd();
8307
8308        return selectionStart >= 0 && selectionStart != selectionEnd;
8309    }
8310
8311    String getSelectedText() {
8312        if (!hasSelection()) {
8313            return null;
8314        }
8315
8316        final int start = getSelectionStart();
8317        final int end = getSelectionEnd();
8318        return String.valueOf(
8319                start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
8320    }
8321
8322    /**
8323     * Sets the properties of this field (lines, horizontally scrolling,
8324     * transformation method) to be for a single-line input.
8325     *
8326     * @attr ref android.R.styleable#TextView_singleLine
8327     */
8328    public void setSingleLine() {
8329        setSingleLine(true);
8330    }
8331
8332    /**
8333     * Sets the properties of this field to transform input to ALL CAPS
8334     * display. This may use a "small caps" formatting if available.
8335     * This setting will be ignored if this field is editable or selectable.
8336     *
8337     * This call replaces the current transformation method. Disabling this
8338     * will not necessarily restore the previous behavior from before this
8339     * was enabled.
8340     *
8341     * @see #setTransformationMethod(TransformationMethod)
8342     * @attr ref android.R.styleable#TextView_textAllCaps
8343     */
8344    public void setAllCaps(boolean allCaps) {
8345        if (allCaps) {
8346            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
8347        } else {
8348            setTransformationMethod(null);
8349        }
8350    }
8351
8352    /**
8353     * If true, sets the properties of this field (number of lines, horizontally scrolling,
8354     * transformation method) to be for a single-line input; if false, restores these to the default
8355     * conditions.
8356     *
8357     * Note that the default conditions are not necessarily those that were in effect prior this
8358     * method, and you may want to reset these properties to your custom values.
8359     *
8360     * @attr ref android.R.styleable#TextView_singleLine
8361     */
8362    @android.view.RemotableViewMethod
8363    public void setSingleLine(boolean singleLine) {
8364        // Could be used, but may break backward compatibility.
8365        // if (mSingleLine == singleLine) return;
8366        setInputTypeSingleLine(singleLine);
8367        applySingleLine(singleLine, true, true);
8368    }
8369
8370    /**
8371     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
8372     * @param singleLine
8373     */
8374    private void setInputTypeSingleLine(boolean singleLine) {
8375        if (mEditor != null
8376                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
8377                        == EditorInfo.TYPE_CLASS_TEXT) {
8378            if (singleLine) {
8379                mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
8380            } else {
8381                mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
8382            }
8383        }
8384    }
8385
8386    private void applySingleLine(boolean singleLine, boolean applyTransformation,
8387            boolean changeMaxLines) {
8388        mSingleLine = singleLine;
8389        if (singleLine) {
8390            setLines(1);
8391            setHorizontallyScrolling(true);
8392            if (applyTransformation) {
8393                setTransformationMethod(SingleLineTransformationMethod.getInstance());
8394            }
8395        } else {
8396            if (changeMaxLines) {
8397                setMaxLines(Integer.MAX_VALUE);
8398            }
8399            setHorizontallyScrolling(false);
8400            if (applyTransformation) {
8401                setTransformationMethod(null);
8402            }
8403        }
8404    }
8405
8406    /**
8407     * Causes words in the text that are longer than the view's width
8408     * to be ellipsized instead of broken in the middle.  You may also
8409     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
8410     * to constrain the text to a single line.  Use <code>null</code>
8411     * to turn off ellipsizing.
8412     *
8413     * If {@link #setMaxLines} has been used to set two or more lines,
8414     * only {@link android.text.TextUtils.TruncateAt#END} and
8415     * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
8416     * (other ellipsizing types will not do anything).
8417     *
8418     * @attr ref android.R.styleable#TextView_ellipsize
8419     */
8420    public void setEllipsize(TextUtils.TruncateAt where) {
8421        // TruncateAt is an enum. != comparison is ok between these singleton objects.
8422        if (mEllipsize != where) {
8423            mEllipsize = where;
8424
8425            if (mLayout != null) {
8426                nullLayouts();
8427                requestLayout();
8428                invalidate();
8429            }
8430        }
8431    }
8432
8433    /**
8434     * Sets how many times to repeat the marquee animation. Only applied if the
8435     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
8436     *
8437     * @see #getMarqueeRepeatLimit()
8438     *
8439     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
8440     */
8441    public void setMarqueeRepeatLimit(int marqueeLimit) {
8442        mMarqueeRepeatLimit = marqueeLimit;
8443    }
8444
8445    /**
8446     * Gets the number of times the marquee animation is repeated. Only meaningful if the
8447     * TextView has marquee enabled.
8448     *
8449     * @return the number of times the marquee animation is repeated. -1 if the animation
8450     * repeats indefinitely
8451     *
8452     * @see #setMarqueeRepeatLimit(int)
8453     *
8454     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
8455     */
8456    public int getMarqueeRepeatLimit() {
8457        return mMarqueeRepeatLimit;
8458    }
8459
8460    /**
8461     * Returns where, if anywhere, words that are longer than the view
8462     * is wide should be ellipsized.
8463     */
8464    @ViewDebug.ExportedProperty
8465    public TextUtils.TruncateAt getEllipsize() {
8466        return mEllipsize;
8467    }
8468
8469    /**
8470     * Set the TextView so that when it takes focus, all the text is
8471     * selected.
8472     *
8473     * @attr ref android.R.styleable#TextView_selectAllOnFocus
8474     */
8475    @android.view.RemotableViewMethod
8476    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
8477        createEditorIfNeeded();
8478        mEditor.mSelectAllOnFocus = selectAllOnFocus;
8479
8480        if (selectAllOnFocus && !(mText instanceof Spannable)) {
8481            setText(mText, BufferType.SPANNABLE);
8482        }
8483    }
8484
8485    /**
8486     * Set whether the cursor is visible. The default is true. Note that this property only
8487     * makes sense for editable TextView.
8488     *
8489     * @see #isCursorVisible()
8490     *
8491     * @attr ref android.R.styleable#TextView_cursorVisible
8492     */
8493    @android.view.RemotableViewMethod
8494    public void setCursorVisible(boolean visible) {
8495        if (visible && mEditor == null) return; // visible is the default value with no edit data
8496        createEditorIfNeeded();
8497        if (mEditor.mCursorVisible != visible) {
8498            mEditor.mCursorVisible = visible;
8499            invalidate();
8500
8501            mEditor.makeBlink();
8502
8503            // InsertionPointCursorController depends on mCursorVisible
8504            mEditor.prepareCursorControllers();
8505        }
8506    }
8507
8508    /**
8509     * @return whether or not the cursor is visible (assuming this TextView is editable)
8510     *
8511     * @see #setCursorVisible(boolean)
8512     *
8513     * @attr ref android.R.styleable#TextView_cursorVisible
8514     */
8515    public boolean isCursorVisible() {
8516        // true is the default value
8517        return mEditor == null ? true : mEditor.mCursorVisible;
8518    }
8519
8520    private boolean canMarquee() {
8521        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8522        return width > 0 && (mLayout.getLineWidth(0) > width
8523                || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
8524                        && mSavedMarqueeModeLayout.getLineWidth(0) > width));
8525    }
8526
8527    private void startMarquee() {
8528        // Do not ellipsize EditText
8529        if (getKeyListener() != null) return;
8530
8531        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8532            return;
8533        }
8534
8535        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
8536                && getLineCount() == 1 && canMarquee()) {
8537
8538            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8539                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8540                final Layout tmp = mLayout;
8541                mLayout = mSavedMarqueeModeLayout;
8542                mSavedMarqueeModeLayout = tmp;
8543                setHorizontalFadingEdgeEnabled(true);
8544                requestLayout();
8545                invalidate();
8546            }
8547
8548            if (mMarquee == null) mMarquee = new Marquee(this);
8549            mMarquee.start(mMarqueeRepeatLimit);
8550        }
8551    }
8552
8553    private void stopMarquee() {
8554        if (mMarquee != null && !mMarquee.isStopped()) {
8555            mMarquee.stop();
8556        }
8557
8558        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8559            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8560            final Layout tmp = mSavedMarqueeModeLayout;
8561            mSavedMarqueeModeLayout = mLayout;
8562            mLayout = tmp;
8563            setHorizontalFadingEdgeEnabled(false);
8564            requestLayout();
8565            invalidate();
8566        }
8567    }
8568
8569    private void startStopMarquee(boolean start) {
8570        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8571            if (start) {
8572                startMarquee();
8573            } else {
8574                stopMarquee();
8575            }
8576        }
8577    }
8578
8579    /**
8580     * This method is called when the text is changed, in case any subclasses
8581     * would like to know.
8582     *
8583     * Within <code>text</code>, the <code>lengthAfter</code> characters
8584     * beginning at <code>start</code> have just replaced old text that had
8585     * length <code>lengthBefore</code>. It is an error to attempt to make
8586     * changes to <code>text</code> from this callback.
8587     *
8588     * @param text The text the TextView is displaying
8589     * @param start The offset of the start of the range of the text that was
8590     * modified
8591     * @param lengthBefore The length of the former text that has been replaced
8592     * @param lengthAfter The length of the replacement modified text
8593     */
8594    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
8595        // intentionally empty, template pattern method can be overridden by subclasses
8596    }
8597
8598    /**
8599     * This method is called when the selection has changed, in case any
8600     * subclasses would like to know.
8601     *
8602     * @param selStart The new selection start location.
8603     * @param selEnd The new selection end location.
8604     */
8605    protected void onSelectionChanged(int selStart, int selEnd) {
8606        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
8607    }
8608
8609    /**
8610     * Adds a TextWatcher to the list of those whose methods are called
8611     * whenever this TextView's text changes.
8612     * <p>
8613     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
8614     * not called after {@link #setText} calls.  Now, doing {@link #setText}
8615     * if there are any text changed listeners forces the buffer type to
8616     * Editable if it would not otherwise be and does call this method.
8617     */
8618    public void addTextChangedListener(TextWatcher watcher) {
8619        if (mListeners == null) {
8620            mListeners = new ArrayList<TextWatcher>();
8621        }
8622
8623        mListeners.add(watcher);
8624    }
8625
8626    /**
8627     * Removes the specified TextWatcher from the list of those whose
8628     * methods are called
8629     * whenever this TextView's text changes.
8630     */
8631    public void removeTextChangedListener(TextWatcher watcher) {
8632        if (mListeners != null) {
8633            int i = mListeners.indexOf(watcher);
8634
8635            if (i >= 0) {
8636                mListeners.remove(i);
8637            }
8638        }
8639    }
8640
8641    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
8642        if (mListeners != null) {
8643            final ArrayList<TextWatcher> list = mListeners;
8644            final int count = list.size();
8645            for (int i = 0; i < count; i++) {
8646                list.get(i).beforeTextChanged(text, start, before, after);
8647            }
8648        }
8649
8650        // The spans that are inside or intersect the modified region no longer make sense
8651        removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8652        removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
8653    }
8654
8655    // Removes all spans that are inside or actually overlap the start..end range
8656    private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
8657        if (!(mText instanceof Editable)) return;
8658        Editable text = (Editable) mText;
8659
8660        T[] spans = text.getSpans(start, end, type);
8661        final int length = spans.length;
8662        for (int i = 0; i < length; i++) {
8663            final int spanStart = text.getSpanStart(spans[i]);
8664            final int spanEnd = text.getSpanEnd(spans[i]);
8665            if (spanEnd == start || spanStart == end) break;
8666            text.removeSpan(spans[i]);
8667        }
8668    }
8669
8670    void removeAdjacentSuggestionSpans(final int pos) {
8671        if (!(mText instanceof Editable)) return;
8672        final Editable text = (Editable) mText;
8673
8674        final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8675        final int length = spans.length;
8676        for (int i = 0; i < length; i++) {
8677            final int spanStart = text.getSpanStart(spans[i]);
8678            final int spanEnd = text.getSpanEnd(spans[i]);
8679            if (spanEnd == pos || spanStart == pos) {
8680                if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8681                    text.removeSpan(spans[i]);
8682                }
8683            }
8684        }
8685    }
8686
8687    /**
8688     * Not private so it can be called from an inner class without going
8689     * through a thunk.
8690     */
8691    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
8692        if (mListeners != null) {
8693            final ArrayList<TextWatcher> list = mListeners;
8694            final int count = list.size();
8695            for (int i = 0; i < count; i++) {
8696                list.get(i).onTextChanged(text, start, before, after);
8697            }
8698        }
8699
8700        if (mEditor != null) mEditor.sendOnTextChanged(start, after);
8701    }
8702
8703    /**
8704     * Not private so it can be called from an inner class without going
8705     * through a thunk.
8706     */
8707    void sendAfterTextChanged(Editable text) {
8708        if (mListeners != null) {
8709            final ArrayList<TextWatcher> list = mListeners;
8710            final int count = list.size();
8711            for (int i = 0; i < count; i++) {
8712                list.get(i).afterTextChanged(text);
8713            }
8714        }
8715        hideErrorIfUnchanged();
8716    }
8717
8718    void updateAfterEdit() {
8719        invalidate();
8720        int curs = getSelectionStart();
8721
8722        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8723            registerForPreDraw();
8724        }
8725
8726        checkForResize();
8727
8728        if (curs >= 0) {
8729            mHighlightPathBogus = true;
8730            if (mEditor != null) mEditor.makeBlink();
8731            bringPointIntoView(curs);
8732        }
8733    }
8734
8735    /**
8736     * Not private so it can be called from an inner class without going
8737     * through a thunk.
8738     */
8739    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8740        sLastCutCopyOrTextChangedTime = 0;
8741
8742        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8743        if (ims == null || ims.mBatchEditNesting == 0) {
8744            updateAfterEdit();
8745        }
8746        if (ims != null) {
8747            ims.mContentChanged = true;
8748            if (ims.mChangedStart < 0) {
8749                ims.mChangedStart = start;
8750                ims.mChangedEnd = start + before;
8751            } else {
8752                ims.mChangedStart = Math.min(ims.mChangedStart, start);
8753                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8754            }
8755            ims.mChangedDelta += after - before;
8756        }
8757        resetErrorChangedFlag();
8758        sendOnTextChanged(buffer, start, before, after);
8759        onTextChanged(buffer, start, before, after);
8760    }
8761
8762    /**
8763     * Not private so it can be called from an inner class without going
8764     * through a thunk.
8765     */
8766    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8767        // XXX Make the start and end move together if this ends up
8768        // spending too much time invalidating.
8769
8770        boolean selChanged = false;
8771        int newSelStart = -1, newSelEnd = -1;
8772
8773        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8774
8775        if (what == Selection.SELECTION_END) {
8776            selChanged = true;
8777            newSelEnd = newStart;
8778
8779            if (oldStart >= 0 || newStart >= 0) {
8780                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8781                checkForResize();
8782                registerForPreDraw();
8783                if (mEditor != null) mEditor.makeBlink();
8784            }
8785        }
8786
8787        if (what == Selection.SELECTION_START) {
8788            selChanged = true;
8789            newSelStart = newStart;
8790
8791            if (oldStart >= 0 || newStart >= 0) {
8792                int end = Selection.getSelectionEnd(buf);
8793                invalidateCursor(end, oldStart, newStart);
8794            }
8795        }
8796
8797        if (selChanged) {
8798            mHighlightPathBogus = true;
8799            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8800
8801            if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
8802                if (newSelStart < 0) {
8803                    newSelStart = Selection.getSelectionStart(buf);
8804                }
8805                if (newSelEnd < 0) {
8806                    newSelEnd = Selection.getSelectionEnd(buf);
8807                }
8808
8809                if (mEditor != null) {
8810                    mEditor.refreshTextActionMode();
8811                    if (!hasSelection() && mEditor.mTextActionMode == null && hasTransientState()) {
8812                        // User generated selection has been removed.
8813                        setHasTransientState(false);
8814                    }
8815                }
8816                onSelectionChanged(newSelStart, newSelEnd);
8817            }
8818        }
8819
8820        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
8821                || what instanceof CharacterStyle) {
8822            if (ims == null || ims.mBatchEditNesting == 0) {
8823                invalidate();
8824                mHighlightPathBogus = true;
8825                checkForResize();
8826            } else {
8827                ims.mContentChanged = true;
8828            }
8829            if (mEditor != null) {
8830                if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8831                if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8832                mEditor.invalidateHandlesAndActionMode();
8833            }
8834        }
8835
8836        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8837            mHighlightPathBogus = true;
8838            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8839                ims.mSelectionModeChanged = true;
8840            }
8841
8842            if (Selection.getSelectionStart(buf) >= 0) {
8843                if (ims == null || ims.mBatchEditNesting == 0) {
8844                    invalidateCursor();
8845                } else {
8846                    ims.mCursorChanged = true;
8847                }
8848            }
8849        }
8850
8851        if (what instanceof ParcelableSpan) {
8852            // If this is a span that can be sent to a remote process,
8853            // the current extract editor would be interested in it.
8854            if (ims != null && ims.mExtractedTextRequest != null) {
8855                if (ims.mBatchEditNesting != 0) {
8856                    if (oldStart >= 0) {
8857                        if (ims.mChangedStart > oldStart) {
8858                            ims.mChangedStart = oldStart;
8859                        }
8860                        if (ims.mChangedStart > oldEnd) {
8861                            ims.mChangedStart = oldEnd;
8862                        }
8863                    }
8864                    if (newStart >= 0) {
8865                        if (ims.mChangedStart > newStart) {
8866                            ims.mChangedStart = newStart;
8867                        }
8868                        if (ims.mChangedStart > newEnd) {
8869                            ims.mChangedStart = newEnd;
8870                        }
8871                    }
8872                } else {
8873                    if (DEBUG_EXTRACT) {
8874                        Log.v(LOG_TAG, "Span change outside of batch: "
8875                                + oldStart + "-" + oldEnd + ","
8876                                + newStart + "-" + newEnd + " " + what);
8877                    }
8878                    ims.mContentChanged = true;
8879                }
8880            }
8881        }
8882
8883        if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
8884                && what instanceof SpellCheckSpan) {
8885            mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8886        }
8887    }
8888
8889    @Override
8890    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8891        if (isTemporarilyDetached()) {
8892            // If we are temporarily in the detach state, then do nothing.
8893            super.onFocusChanged(focused, direction, previouslyFocusedRect);
8894            return;
8895        }
8896
8897        if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8898
8899        if (focused) {
8900            if (mText instanceof Spannable) {
8901                Spannable sp = (Spannable) mText;
8902                MetaKeyKeyListener.resetMetaState(sp);
8903            }
8904        }
8905
8906        startStopMarquee(focused);
8907
8908        if (mTransformation != null) {
8909            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8910        }
8911
8912        super.onFocusChanged(focused, direction, previouslyFocusedRect);
8913    }
8914
8915    @Override
8916    public void onWindowFocusChanged(boolean hasWindowFocus) {
8917        super.onWindowFocusChanged(hasWindowFocus);
8918
8919        if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8920
8921        startStopMarquee(hasWindowFocus);
8922    }
8923
8924    @Override
8925    protected void onVisibilityChanged(View changedView, int visibility) {
8926        super.onVisibilityChanged(changedView, visibility);
8927        if (mEditor != null && visibility != VISIBLE) {
8928            mEditor.hideCursorAndSpanControllers();
8929            stopTextActionMode();
8930        }
8931    }
8932
8933    /**
8934     * Use {@link BaseInputConnection#removeComposingSpans
8935     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8936     * state from this text view.
8937     */
8938    public void clearComposingText() {
8939        if (mText instanceof Spannable) {
8940            BaseInputConnection.removeComposingSpans((Spannable) mText);
8941        }
8942    }
8943
8944    @Override
8945    public void setSelected(boolean selected) {
8946        boolean wasSelected = isSelected();
8947
8948        super.setSelected(selected);
8949
8950        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8951            if (selected) {
8952                startMarquee();
8953            } else {
8954                stopMarquee();
8955            }
8956        }
8957    }
8958
8959    @Override
8960    public boolean onTouchEvent(MotionEvent event) {
8961        final int action = event.getActionMasked();
8962        if (mEditor != null) {
8963            mEditor.onTouchEvent(event);
8964
8965            if (mEditor.mSelectionModifierCursorController != null
8966                    && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8967                return true;
8968            }
8969        }
8970
8971        final boolean superResult = super.onTouchEvent(event);
8972
8973        /*
8974         * Don't handle the release after a long press, because it will move the selection away from
8975         * whatever the menu action was trying to affect. If the long press should have triggered an
8976         * insertion action mode, we can now actually show it.
8977         */
8978        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8979            mEditor.mDiscardNextActionUp = false;
8980
8981            if (mEditor.mIsInsertionActionModeStartPending) {
8982                mEditor.startInsertionActionMode();
8983                mEditor.mIsInsertionActionModeStartPending = false;
8984            }
8985            return superResult;
8986        }
8987
8988        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
8989                && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8990
8991        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8992                && mText instanceof Spannable && mLayout != null) {
8993            boolean handled = false;
8994
8995            if (mMovement != null) {
8996                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8997            }
8998
8999            // Lazily create the clickable span gesture detector only if it looks like it
9000            // might be useful.
9001            if (action == MotionEvent.ACTION_DOWN && mClickableSpanOnClickGestureDetector == null
9002                    && shouldUseClickableSpanOnClickGestureDetector()) {
9003                ClickableSpan[] links = ((Spannable) mText).getSpans(
9004                        getSelectionStart(), getSelectionEnd(),
9005                        ClickableSpan.class);
9006                if (links.length > 0) {
9007                    mClickableSpanOnClickGestureDetector =
9008                            createClickableSpanOnClickGestureDetector();
9009                }
9010            }
9011
9012            if (mClickableSpanOnClickGestureDetector != null) {
9013                handled |= mClickableSpanOnClickGestureDetector.onTouchEvent(event);
9014            }
9015
9016            if (touchIsFinished && (isTextEditable() || isTextSelectable())) {
9017                // Show the IME, except when selecting in read-only text.
9018                final InputMethodManager imm = InputMethodManager.peekInstance();
9019                viewClicked(imm);
9020                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9021                    imm.showSoftInput(this, 0);
9022                }
9023
9024                // The above condition ensures that the mEditor is not null
9025                mEditor.onTouchUpEvent(event);
9026
9027                handled = true;
9028            }
9029
9030            if (handled) {
9031                return true;
9032            }
9033        }
9034
9035        return superResult;
9036    }
9037
9038    @Override
9039    public boolean onGenericMotionEvent(MotionEvent event) {
9040        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9041            try {
9042                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
9043                    return true;
9044                }
9045            } catch (AbstractMethodError ex) {
9046                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
9047                // Ignore its absence in case third party applications implemented the
9048                // interface directly.
9049            }
9050        }
9051        return super.onGenericMotionEvent(event);
9052    }
9053
9054    @Override
9055    protected void onCreateContextMenu(ContextMenu menu) {
9056        if (mEditor != null) {
9057            mEditor.onCreateContextMenu(menu);
9058        }
9059    }
9060
9061    @Override
9062    public boolean showContextMenu() {
9063        if (mEditor != null) {
9064            mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
9065        }
9066        return super.showContextMenu();
9067    }
9068
9069    @Override
9070    public boolean showContextMenu(float x, float y) {
9071        if (mEditor != null) {
9072            mEditor.setContextMenuAnchor(x, y);
9073        }
9074        return super.showContextMenu(x, y);
9075    }
9076
9077    /**
9078     * @return True iff this TextView contains a text that can be edited, or if this is
9079     * a selectable TextView.
9080     */
9081    boolean isTextEditable() {
9082        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
9083    }
9084
9085    /**
9086     * Returns true, only while processing a touch gesture, if the initial
9087     * touch down event caused focus to move to the text view and as a result
9088     * its selection changed.  Only valid while processing the touch gesture
9089     * of interest, in an editable text view.
9090     */
9091    public boolean didTouchFocusSelect() {
9092        return mEditor != null && mEditor.mTouchFocusSelected;
9093    }
9094
9095    @Override
9096    public void cancelLongPress() {
9097        super.cancelLongPress();
9098        if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
9099    }
9100
9101    @Override
9102    public boolean onTrackballEvent(MotionEvent event) {
9103        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9104            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
9105                return true;
9106            }
9107        }
9108
9109        return super.onTrackballEvent(event);
9110    }
9111
9112    public void setScroller(Scroller s) {
9113        mScroller = s;
9114    }
9115
9116    @Override
9117    protected float getLeftFadingEdgeStrength() {
9118        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9119            final Marquee marquee = mMarquee;
9120            if (marquee.shouldDrawLeftFade()) {
9121                return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
9122            } else {
9123                return 0.0f;
9124            }
9125        } else if (getLineCount() == 1) {
9126            final float lineLeft = getLayout().getLineLeft(0);
9127            if (lineLeft > mScrollX) return 0.0f;
9128            return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
9129        }
9130        return super.getLeftFadingEdgeStrength();
9131    }
9132
9133    @Override
9134    protected float getRightFadingEdgeStrength() {
9135        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9136            final Marquee marquee = mMarquee;
9137            return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
9138        } else if (getLineCount() == 1) {
9139            final float rightEdge = mScrollX +
9140                    (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
9141            final float lineRight = getLayout().getLineRight(0);
9142            if (lineRight < rightEdge) return 0.0f;
9143            return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
9144        }
9145        return super.getRightFadingEdgeStrength();
9146    }
9147
9148    /**
9149     * Calculates the fading edge strength as the ratio of the distance between two
9150     * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
9151     * value for the distance calculation.
9152     *
9153     * @param position1 A horizontal position.
9154     * @param position2 A horizontal position.
9155     * @return Fading edge strength between [0.0f, 1.0f].
9156     */
9157    @FloatRange(from = 0.0, to = 1.0)
9158    private float getHorizontalFadingEdgeStrength(float position1, float position2) {
9159        final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
9160        if (horizontalFadingEdgeLength == 0) return 0.0f;
9161        final float diff = Math.abs(position1 - position2);
9162        if (diff > horizontalFadingEdgeLength) return 1.0f;
9163        return diff / horizontalFadingEdgeLength;
9164    }
9165
9166    private boolean isMarqueeFadeEnabled() {
9167        return mEllipsize == TextUtils.TruncateAt.MARQUEE
9168                && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9169    }
9170
9171    @Override
9172    protected int computeHorizontalScrollRange() {
9173        if (mLayout != null) {
9174            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
9175                    ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
9176        }
9177
9178        return super.computeHorizontalScrollRange();
9179    }
9180
9181    @Override
9182    protected int computeVerticalScrollRange() {
9183        if (mLayout != null) {
9184            return mLayout.getHeight();
9185        }
9186        return super.computeVerticalScrollRange();
9187    }
9188
9189    @Override
9190    protected int computeVerticalScrollExtent() {
9191        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
9192    }
9193
9194    @Override
9195    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
9196        super.findViewsWithText(outViews, searched, flags);
9197        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
9198                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
9199            String searchedLowerCase = searched.toString().toLowerCase();
9200            String textLowerCase = mText.toString().toLowerCase();
9201            if (textLowerCase.contains(searchedLowerCase)) {
9202                outViews.add(this);
9203            }
9204        }
9205    }
9206
9207    /**
9208     * Type of the text buffer that defines the characteristics of the text such as static,
9209     * styleable, or editable.
9210     */
9211    public enum BufferType {
9212        NORMAL, SPANNABLE, EDITABLE
9213    }
9214
9215    /**
9216     * Returns the TextView_textColor attribute from the TypedArray, if set, or
9217     * the TextAppearance_textColor from the TextView_textAppearance attribute,
9218     * if TextView_textColor was not set directly.
9219     *
9220     * @removed
9221     */
9222    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
9223        if (attrs == null) {
9224            // Preserve behavior prior to removal of this API.
9225            throw new NullPointerException();
9226        }
9227
9228        // It's not safe to use this method from apps. The parameter 'attrs'
9229        // must have been obtained using the TextView filter array which is not
9230        // available to the SDK. As such, we grab a default TypedArray with the
9231        // right filter instead here.
9232        final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
9233        ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
9234        if (colors == null) {
9235            final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
9236            if (ap != 0) {
9237                final TypedArray appearance = context.obtainStyledAttributes(
9238                        ap, R.styleable.TextAppearance);
9239                colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
9240                appearance.recycle();
9241            }
9242        }
9243        a.recycle();
9244
9245        return colors;
9246    }
9247
9248    /**
9249     * Returns the default color from the TextView_textColor attribute from the
9250     * AttributeSet, if set, or the default color from the
9251     * TextAppearance_textColor from the TextView_textAppearance attribute, if
9252     * TextView_textColor was not set directly.
9253     *
9254     * @removed
9255     */
9256    public static int getTextColor(Context context, TypedArray attrs, int def) {
9257        final ColorStateList colors = getTextColors(context, attrs);
9258        if (colors == null) {
9259            return def;
9260        } else {
9261            return colors.getDefaultColor();
9262        }
9263    }
9264
9265    @Override
9266    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
9267        if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
9268            // Handle Ctrl-only shortcuts.
9269            switch (keyCode) {
9270                case KeyEvent.KEYCODE_A:
9271                    if (canSelectText()) {
9272                        return onTextContextMenuItem(ID_SELECT_ALL);
9273                    }
9274                    break;
9275                case KeyEvent.KEYCODE_Z:
9276                    if (canUndo()) {
9277                        return onTextContextMenuItem(ID_UNDO);
9278                    }
9279                    break;
9280                case KeyEvent.KEYCODE_X:
9281                    if (canCut()) {
9282                        return onTextContextMenuItem(ID_CUT);
9283                    }
9284                    break;
9285                case KeyEvent.KEYCODE_C:
9286                    if (canCopy()) {
9287                        return onTextContextMenuItem(ID_COPY);
9288                    }
9289                    break;
9290                case KeyEvent.KEYCODE_V:
9291                    if (canPaste()) {
9292                        return onTextContextMenuItem(ID_PASTE);
9293                    }
9294                    break;
9295            }
9296        } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
9297            // Handle Ctrl-Shift shortcuts.
9298            switch (keyCode) {
9299                case KeyEvent.KEYCODE_Z:
9300                    if (canRedo()) {
9301                        return onTextContextMenuItem(ID_REDO);
9302                    }
9303                    break;
9304                case KeyEvent.KEYCODE_V:
9305                    if (canPaste()) {
9306                        return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
9307                    }
9308            }
9309        }
9310        return super.onKeyShortcut(keyCode, event);
9311    }
9312
9313    /**
9314     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
9315     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
9316     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
9317     * sufficient.
9318     */
9319    boolean canSelectText() {
9320        return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
9321    }
9322
9323    /**
9324     * Test based on the <i>intrinsic</i> charateristics of the TextView.
9325     * The text must be spannable and the movement method must allow for arbitary selection.
9326     *
9327     * See also {@link #canSelectText()}.
9328     */
9329    boolean textCanBeSelected() {
9330        // prepareCursorController() relies on this method.
9331        // If you change this condition, make sure prepareCursorController is called anywhere
9332        // the value of this condition might be changed.
9333        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
9334        return isTextEditable()
9335                || (isTextSelectable() && mText instanceof Spannable && isEnabled());
9336    }
9337
9338    private Locale getTextServicesLocale(boolean allowNullLocale) {
9339        // Start fetching the text services locale asynchronously.
9340        updateTextServicesLocaleAsync();
9341        // If !allowNullLocale and there is no cached text services locale, just return the default
9342        // locale.
9343        return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
9344                : mCurrentSpellCheckerLocaleCache;
9345    }
9346
9347    /**
9348     * This is a temporary method. Future versions may support multi-locale text.
9349     * Caveat: This method may not return the latest text services locale, but this should be
9350     * acceptable and it's more important to make this method asynchronous.
9351     *
9352     * @return The locale that should be used for a word iterator
9353     * in this TextView, based on the current spell checker settings,
9354     * the current IME's locale, or the system default locale.
9355     * Please note that a word iterator in this TextView is different from another word iterator
9356     * used by SpellChecker.java of TextView. This method should be used for the former.
9357     * @hide
9358     */
9359    // TODO: Support multi-locale
9360    // TODO: Update the text services locale immediately after the keyboard locale is switched
9361    // by catching intent of keyboard switch event
9362    public Locale getTextServicesLocale() {
9363        return getTextServicesLocale(false /* allowNullLocale */);
9364    }
9365
9366    /**
9367     * @return {@code true} if this TextView is specialized for showing and interacting with the
9368     * extracted text in a full-screen input method.
9369     * @hide
9370     */
9371    public boolean isInExtractedMode() {
9372        return false;
9373    }
9374
9375    /**
9376     * @return {@code true} if this widget supports auto-sizing text and has been configured to
9377     * auto-size.
9378     */
9379    private boolean isAutoSizeEnabled() {
9380        return supportsAutoSizeText() && mAutoSizeType != AUTO_SIZE_TYPE_NONE;
9381    }
9382
9383    /**
9384     * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
9385     * @hide
9386     */
9387    protected boolean supportsAutoSizeText() {
9388        return true;
9389    }
9390
9391    /**
9392     * This is a temporary method. Future versions may support multi-locale text.
9393     * Caveat: This method may not return the latest spell checker locale, but this should be
9394     * acceptable and it's more important to make this method asynchronous.
9395     *
9396     * @return The locale that should be used for a spell checker in this TextView,
9397     * based on the current spell checker settings, the current IME's locale, or the system default
9398     * locale.
9399     * @hide
9400     */
9401    public Locale getSpellCheckerLocale() {
9402        return getTextServicesLocale(true /* allowNullLocale */);
9403    }
9404
9405    private void updateTextServicesLocaleAsync() {
9406        // AsyncTask.execute() uses a serial executor which means we don't have
9407        // to lock around updateTextServicesLocaleLocked() to prevent it from
9408        // being executed n times in parallel.
9409        AsyncTask.execute(new Runnable() {
9410            @Override
9411            public void run() {
9412                updateTextServicesLocaleLocked();
9413            }
9414        });
9415    }
9416
9417    private void updateTextServicesLocaleLocked() {
9418        final TextServicesManager textServicesManager = (TextServicesManager)
9419                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
9420        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
9421        final Locale locale;
9422        if (subtype != null) {
9423            locale = subtype.getLocaleObject();
9424        } else {
9425            locale = null;
9426        }
9427        mCurrentSpellCheckerLocaleCache = locale;
9428    }
9429
9430    void onLocaleChanged() {
9431        mEditor.onLocaleChanged();
9432    }
9433
9434    private GestureDetector createClickableSpanOnClickGestureDetector() {
9435        return new GestureDetector(mContext,
9436                new GestureDetector.SimpleOnGestureListener() {
9437                    @Override
9438                    public boolean onSingleTapConfirmed(MotionEvent e) {
9439                        if (shouldUseClickableSpanOnClickGestureDetector()) {
9440                            ClickableSpan[] links = ((Spannable) mText).getSpans(
9441                                    getSelectionStart(), getSelectionEnd(),
9442                                    ClickableSpan.class);
9443                            if (links.length > 0) {
9444                                links[0].onClick(TextView.this);
9445                                return true;
9446                            }
9447                        }
9448                        return false;
9449                    }
9450                });
9451    }
9452
9453    private boolean shouldUseClickableSpanOnClickGestureDetector() {
9454        return mLinksClickable && (mMovement != null) &&
9455                (mMovement instanceof LinkMovementMethod
9456                        || (mAutoLinkMask != 0 && isTextSelectable()));
9457    }
9458
9459    /**
9460     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
9461     * Made available to achieve a consistent behavior.
9462     * @hide
9463     */
9464    public WordIterator getWordIterator() {
9465        if (mEditor != null) {
9466            return mEditor.getWordIterator();
9467        } else {
9468            return null;
9469        }
9470    }
9471
9472    /** @hide */
9473    @Override
9474    public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
9475        super.onPopulateAccessibilityEventInternal(event);
9476
9477        final CharSequence text = getTextForAccessibility();
9478        if (!TextUtils.isEmpty(text)) {
9479            event.getText().add(text);
9480        }
9481    }
9482
9483    /**
9484     * @return true if the user has explicitly allowed accessibility services
9485     * to speak passwords.
9486     */
9487    private boolean shouldSpeakPasswordsForAccessibility() {
9488        return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
9489                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
9490                UserHandle.USER_CURRENT_OR_SELF) == 1);
9491    }
9492
9493    @Override
9494    public CharSequence getAccessibilityClassName() {
9495        return TextView.class.getName();
9496    }
9497
9498    @Override
9499    public void onProvideStructure(ViewStructure structure) {
9500        super.onProvideStructure(structure);
9501        onProvideAutoStructureForAssistOrAutoFill(structure, 0);
9502    }
9503
9504    @Override
9505    public void onProvideAutoFillStructure(ViewStructure structure, int flags) {
9506        super.onProvideAutoFillStructure(structure, flags);
9507        onProvideAutoStructureForAssistOrAutoFill(structure, flags);
9508    }
9509
9510    private void onProvideAutoStructureForAssistOrAutoFill(ViewStructure structure, int flags) {
9511        // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
9512        // this method should take a boolean with the type of request.
9513        final boolean forAutoFillSave =
9514                (flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0;
9515
9516        final boolean isPassword = hasPasswordTransformationMethod()
9517                || isPasswordInputType(getInputType());
9518        if (!isPassword || forAutoFillSave) {
9519            if (mLayout == null) {
9520                assumeLayout();
9521            }
9522            Layout layout = mLayout;
9523            final int lineCount = layout.getLineCount();
9524            if (lineCount <= 1) {
9525                // Simple case: this is a single line.
9526                structure.setText(getText(), getSelectionStart(), getSelectionEnd());
9527            } else {
9528                // Complex case: multi-line, could be scrolled or within a scroll container
9529                // so some lines are not visible.
9530                final int[] tmpCords = new int[2];
9531                getLocationInWindow(tmpCords);
9532                final int topWindowLocation = tmpCords[1];
9533                View root = this;
9534                ViewParent viewParent = getParent();
9535                while (viewParent instanceof View) {
9536                    root = (View) viewParent;
9537                    viewParent = root.getParent();
9538                }
9539                final int windowHeight = root.getHeight();
9540                final int topLine;
9541                final int bottomLine;
9542                if (topWindowLocation >= 0) {
9543                    // The top of the view is fully within its window; start text at line 0.
9544                    topLine = getLineAtCoordinateUnclamped(0);
9545                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
9546                } else {
9547                    // The top of hte window has scrolled off the top of the window; figure out
9548                    // the starting line for this.
9549                    topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
9550                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
9551                }
9552                // We want to return some contextual lines above/below the lines that are
9553                // actually visible.
9554                int expandedTopLine = topLine - (bottomLine - topLine) / 2;
9555                if (expandedTopLine < 0) {
9556                    expandedTopLine = 0;
9557                }
9558                int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
9559                if (expandedBottomLine >= lineCount) {
9560                    expandedBottomLine = lineCount - 1;
9561                }
9562                // Convert lines into character offsets.
9563                int expandedTopChar = layout.getLineStart(expandedTopLine);
9564                int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
9565                // Take into account selection -- if there is a selection, we need to expand
9566                // the text we are returning to include that selection.
9567                final int selStart = getSelectionStart();
9568                final int selEnd = getSelectionEnd();
9569                if (selStart < selEnd) {
9570                    if (selStart < expandedTopChar) {
9571                        expandedTopChar = selStart;
9572                    }
9573                    if (selEnd > expandedBottomChar) {
9574                        expandedBottomChar = selEnd;
9575                    }
9576                }
9577                // Get the text and trim it to the range we are reporting.
9578                CharSequence text = getText();
9579                if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9580                    text = text.subSequence(expandedTopChar, expandedBottomChar);
9581                }
9582                structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
9583                final int[] lineOffsets = new int[bottomLine - topLine + 1];
9584                final int[] lineBaselines = new int[bottomLine - topLine + 1];
9585                final int baselineOffset = getBaselineOffset();
9586                for (int i = topLine; i <= bottomLine; i++) {
9587                    lineOffsets[i - topLine] = layout.getLineStart(i);
9588                    lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
9589                }
9590                structure.setTextLines(lineOffsets, lineBaselines);
9591            }
9592
9593            // Extract style information that applies to the TextView as a whole.
9594            int style = 0;
9595            int typefaceStyle = getTypefaceStyle();
9596            if ((typefaceStyle & Typeface.BOLD) != 0) {
9597                style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9598            }
9599            if ((typefaceStyle & Typeface.ITALIC) != 0) {
9600                style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9601            }
9602
9603            // Global styles can also be set via TextView.setPaintFlags().
9604            int paintFlags = mTextPaint.getFlags();
9605            if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9606                style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9607            }
9608            if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9609                style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9610            }
9611            if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
9612                style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
9613            }
9614
9615            // TextView does not have its own text background color. A background is either part
9616            // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
9617            structure.setTextStyle(getTextSize(), getCurrentTextColor(),
9618                    AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
9619        }
9620        structure.setHint(getHint());
9621    }
9622
9623    /** @hide */
9624    @Override
9625    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
9626        super.onInitializeAccessibilityEventInternal(event);
9627
9628        final boolean isPassword = hasPasswordTransformationMethod();
9629        event.setPassword(isPassword);
9630
9631        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
9632            event.setFromIndex(Selection.getSelectionStart(mText));
9633            event.setToIndex(Selection.getSelectionEnd(mText));
9634            event.setItemCount(mText.length());
9635        }
9636    }
9637
9638    /** @hide */
9639    @Override
9640    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
9641        super.onInitializeAccessibilityNodeInfoInternal(info);
9642
9643        final boolean isPassword = hasPasswordTransformationMethod();
9644        info.setPassword(isPassword);
9645        info.setText(getTextForAccessibility());
9646        info.setHintText(mHint);
9647        info.setShowingHintText(isShowingHint());
9648
9649        if (mBufferType == BufferType.EDITABLE) {
9650            info.setEditable(true);
9651            if (isEnabled()) {
9652                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
9653            }
9654        }
9655
9656        if (mEditor != null) {
9657            info.setInputType(mEditor.mInputType);
9658
9659            if (mEditor.mError != null) {
9660                info.setContentInvalid(true);
9661                info.setError(mEditor.mError);
9662            }
9663        }
9664
9665        if (!TextUtils.isEmpty(mText)) {
9666            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
9667            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
9668            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
9669                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
9670                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
9671                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
9672                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
9673            info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
9674        }
9675
9676        if (isFocused()) {
9677            if (canCopy()) {
9678                info.addAction(AccessibilityNodeInfo.ACTION_COPY);
9679            }
9680            if (canPaste()) {
9681                info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
9682            }
9683            if (canCut()) {
9684                info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9685            }
9686            if (canShare()) {
9687                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9688                        ACCESSIBILITY_ACTION_SHARE,
9689                        getResources().getString(com.android.internal.R.string.share)));
9690            }
9691            if (canProcessText()) {  // also implies mEditor is not null.
9692                mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9693            }
9694        }
9695
9696        // Check for known input filter types.
9697        final int numFilters = mFilters.length;
9698        for (int i = 0; i < numFilters; i++) {
9699            final InputFilter filter = mFilters[i];
9700            if (filter instanceof InputFilter.LengthFilter) {
9701                info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9702            }
9703        }
9704
9705        if (!isSingleLine()) {
9706            info.setMultiLine(true);
9707        }
9708    }
9709
9710    /**
9711     * Performs an accessibility action after it has been offered to the
9712     * delegate.
9713     *
9714     * @hide
9715     */
9716    @Override
9717    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
9718        if (mEditor != null
9719                && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9720            return true;
9721        }
9722        switch (action) {
9723            case AccessibilityNodeInfo.ACTION_CLICK: {
9724                return performAccessibilityActionClick(arguments);
9725            }
9726            case AccessibilityNodeInfo.ACTION_COPY: {
9727                if (isFocused() && canCopy()) {
9728                    if (onTextContextMenuItem(ID_COPY)) {
9729                        return true;
9730                    }
9731                }
9732            } return false;
9733            case AccessibilityNodeInfo.ACTION_PASTE: {
9734                if (isFocused() && canPaste()) {
9735                    if (onTextContextMenuItem(ID_PASTE)) {
9736                        return true;
9737                    }
9738                }
9739            } return false;
9740            case AccessibilityNodeInfo.ACTION_CUT: {
9741                if (isFocused() && canCut()) {
9742                    if (onTextContextMenuItem(ID_CUT)) {
9743                        return true;
9744                    }
9745                }
9746            } return false;
9747            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
9748                ensureIterableTextForAccessibilitySelectable();
9749                CharSequence text = getIterableTextForAccessibility();
9750                if (text == null) {
9751                    return false;
9752                }
9753                final int start = (arguments != null) ? arguments.getInt(
9754                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9755                final int end = (arguments != null) ? arguments.getInt(
9756                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9757                if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9758                    // No arguments clears the selection.
9759                    if (start == end && end == -1) {
9760                        Selection.removeSelection((Spannable) text);
9761                        return true;
9762                    }
9763                    if (start >= 0 && start <= end && end <= text.length()) {
9764                        Selection.setSelection((Spannable) text, start, end);
9765                        // Make sure selection mode is engaged.
9766                        if (mEditor != null) {
9767                            mEditor.startSelectionActionMode();
9768                        }
9769                        return true;
9770                    }
9771                }
9772            } return false;
9773            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9774            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9775                ensureIterableTextForAccessibilitySelectable();
9776                return super.performAccessibilityActionInternal(action, arguments);
9777            }
9778            case ACCESSIBILITY_ACTION_SHARE: {
9779                if (isFocused() && canShare()) {
9780                    if (onTextContextMenuItem(ID_SHARE)) {
9781                        return true;
9782                    }
9783                }
9784            } return false;
9785            case AccessibilityNodeInfo.ACTION_SET_TEXT: {
9786                if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
9787                    return false;
9788                }
9789                CharSequence text = (arguments != null) ? arguments.getCharSequence(
9790                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
9791                setText(text);
9792                if (mText != null) {
9793                    int updatedTextLength = mText.length();
9794                    if (updatedTextLength > 0) {
9795                        Selection.setSelection((Spannable) mText, updatedTextLength);
9796                    }
9797                }
9798            } return true;
9799            default: {
9800                return super.performAccessibilityActionInternal(action, arguments);
9801            }
9802        }
9803    }
9804
9805    private boolean performAccessibilityActionClick(Bundle arguments) {
9806        boolean handled = false;
9807
9808        if (!isEnabled()) {
9809            return false;
9810        }
9811
9812        if (isClickable() || isLongClickable()) {
9813            // Simulate View.onTouchEvent for an ACTION_UP event
9814            if (isFocusable() && !isFocused()) {
9815                requestFocus();
9816            }
9817
9818            performClick();
9819            handled = true;
9820        }
9821
9822        // Show the IME, except when selecting in read-only text.
9823        if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9824                && (isTextEditable() || isTextSelectable()) && isFocused()) {
9825            final InputMethodManager imm = InputMethodManager.peekInstance();
9826            viewClicked(imm);
9827            if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9828                handled |= imm.showSoftInput(this, 0);
9829            }
9830        }
9831
9832        return handled;
9833    }
9834
9835    private boolean hasSpannableText() {
9836        return mText != null && mText instanceof Spannable;
9837    }
9838
9839    /** @hide */
9840    @Override
9841    public void sendAccessibilityEventInternal(int eventType) {
9842        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9843            mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9844        }
9845
9846        // Do not send scroll events since first they are not interesting for
9847        // accessibility and second such events a generated too frequently.
9848        // For details see the implementation of bringTextIntoView().
9849        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9850            return;
9851        }
9852        super.sendAccessibilityEventInternal(eventType);
9853    }
9854
9855    /**
9856     * Returns the text that should be exposed to accessibility services.
9857     * <p>
9858     * This approximates what is displayed visually. If the user has specified
9859     * that accessibility services should speak passwords, this method will
9860     * bypass any password transformation method and return unobscured text.
9861     *
9862     * @return the text that should be exposed to accessibility services, may
9863     *         be {@code null} if no text is set
9864     */
9865    @Nullable
9866    private CharSequence getTextForAccessibility() {
9867        // If the text is empty, we must be showing the hint text.
9868        if (TextUtils.isEmpty(mText)) {
9869            return mHint;
9870        }
9871
9872        // Check whether we need to bypass the transformation
9873        // method and expose unobscured text.
9874        if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9875            return mText;
9876        }
9877
9878        // Otherwise, speak whatever text is being displayed.
9879        return mTransformed;
9880    }
9881
9882    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9883            int fromIndex, int removedCount, int addedCount) {
9884        AccessibilityEvent event =
9885                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9886        event.setFromIndex(fromIndex);
9887        event.setRemovedCount(removedCount);
9888        event.setAddedCount(addedCount);
9889        event.setBeforeText(beforeText);
9890        sendAccessibilityEventUnchecked(event);
9891    }
9892
9893    /**
9894     * Returns whether this text view is a current input method target.  The
9895     * default implementation just checks with {@link InputMethodManager}.
9896     */
9897    public boolean isInputMethodTarget() {
9898        InputMethodManager imm = InputMethodManager.peekInstance();
9899        return imm != null && imm.isActive(this);
9900    }
9901
9902    static final int ID_SELECT_ALL = android.R.id.selectAll;
9903    static final int ID_UNDO = android.R.id.undo;
9904    static final int ID_REDO = android.R.id.redo;
9905    static final int ID_CUT = android.R.id.cut;
9906    static final int ID_COPY = android.R.id.copy;
9907    static final int ID_PASTE = android.R.id.paste;
9908    static final int ID_SHARE = android.R.id.shareText;
9909    static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9910    static final int ID_REPLACE = android.R.id.replaceText;
9911
9912    /**
9913     * Called when a context menu option for the text view is selected.  Currently
9914     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9915     * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9916     *
9917     * @return true if the context menu item action was performed.
9918     */
9919    public boolean onTextContextMenuItem(int id) {
9920        int min = 0;
9921        int max = mText.length();
9922
9923        if (isFocused()) {
9924            final int selStart = getSelectionStart();
9925            final int selEnd = getSelectionEnd();
9926
9927            min = Math.max(0, Math.min(selStart, selEnd));
9928            max = Math.max(0, Math.max(selStart, selEnd));
9929        }
9930
9931        switch (id) {
9932            case ID_SELECT_ALL:
9933                selectAllText();
9934                return true;
9935
9936            case ID_UNDO:
9937                if (mEditor != null) {
9938                    mEditor.undo();
9939                }
9940                return true;  // Returns true even if nothing was undone.
9941
9942            case ID_REDO:
9943                if (mEditor != null) {
9944                    mEditor.redo();
9945                }
9946                return true;  // Returns true even if nothing was undone.
9947
9948            case ID_PASTE:
9949                paste(min, max, true /* withFormatting */);
9950                return true;
9951
9952            case ID_PASTE_AS_PLAIN_TEXT:
9953                paste(min, max, false /* withFormatting */);
9954                return true;
9955
9956            case ID_CUT:
9957                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9958                deleteText_internal(min, max);
9959                return true;
9960
9961            case ID_COPY:
9962                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9963                stopTextActionMode();
9964                return true;
9965
9966            case ID_REPLACE:
9967                if (mEditor != null) {
9968                    mEditor.replace();
9969                }
9970                return true;
9971
9972            case ID_SHARE:
9973                shareSelectedText();
9974                return true;
9975        }
9976        return false;
9977    }
9978
9979    CharSequence getTransformedText(int start, int end) {
9980        return removeSuggestionSpans(mTransformed.subSequence(start, end));
9981    }
9982
9983    @Override
9984    public boolean performLongClick() {
9985        boolean handled = false;
9986
9987        if (mEditor != null) {
9988            mEditor.mIsBeingLongClicked = true;
9989        }
9990
9991        if (super.performLongClick()) {
9992            handled = true;
9993        }
9994
9995        if (mEditor != null) {
9996            handled |= mEditor.performLongClick(handled);
9997            mEditor.mIsBeingLongClicked = false;
9998        }
9999
10000        if (handled) {
10001            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
10002            if (mEditor != null) mEditor.mDiscardNextActionUp = true;
10003        } else {
10004            MetricsLogger.action(
10005                    mContext,
10006                    MetricsEvent.TEXT_LONGPRESS,
10007                    TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
10008        }
10009
10010        return handled;
10011    }
10012
10013    @Override
10014    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
10015        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
10016        if (mEditor != null) {
10017            mEditor.onScrollChanged();
10018        }
10019    }
10020
10021    /**
10022     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
10023     * by the IME or by the spell checker as the user types. This is done by adding
10024     * {@link SuggestionSpan}s to the text.
10025     *
10026     * When suggestions are enabled (default), this list of suggestions will be displayed when the
10027     * user asks for them on these parts of the text. This value depends on the inputType of this
10028     * TextView.
10029     *
10030     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
10031     *
10032     * In addition, the type variation must be one of
10033     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
10034     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
10035     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
10036     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
10037     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
10038     *
10039     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
10040     *
10041     * @return true if the suggestions popup window is enabled, based on the inputType.
10042     */
10043    public boolean isSuggestionsEnabled() {
10044        if (mEditor == null) return false;
10045        if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
10046            return false;
10047        }
10048        if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
10049
10050        final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
10051        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
10052                || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
10053                || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
10054                || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
10055                || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
10056    }
10057
10058    /**
10059     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10060     * selection is initiated in this View.
10061     *
10062     * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
10063     * Paste, Replace and Share actions, depending on what this View supports.
10064     *
10065     * <p>A custom implementation can add new entries in the default menu in its
10066     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
10067     * method. The default actions can also be removed from the menu using
10068     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
10069     * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
10070     * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
10071     *
10072     * <p>Returning false from
10073     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
10074     * will prevent the action mode from being started.
10075     *
10076     * <p>Action click events should be handled by the custom implementation of
10077     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
10078     * android.view.MenuItem)}.
10079     *
10080     * <p>Note that text selection mode is not started when a TextView receives focus and the
10081     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
10082     * that case, to allow for quick replacement.
10083     */
10084    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
10085        createEditorIfNeeded();
10086        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
10087    }
10088
10089    /**
10090     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
10091     *
10092     * @return The current custom selection callback.
10093     */
10094    public ActionMode.Callback getCustomSelectionActionModeCallback() {
10095        return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
10096    }
10097
10098    /**
10099     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10100     * insertion is initiated in this View.
10101     * The standard implementation populates the menu with a subset of Select All,
10102     * Paste and Replace actions, depending on what this View supports.
10103     *
10104     * <p>A custom implementation can add new entries in the default menu in its
10105     * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
10106     * android.view.Menu)} method. The default actions can also be removed from the menu using
10107     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
10108     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
10109     *
10110     * <p>Returning false from
10111     * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
10112     * android.view.Menu)} will prevent the action mode from being started.</p>
10113     *
10114     * <p>Action click events should be handled by the custom implementation of
10115     * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
10116     * android.view.MenuItem)}.</p>
10117     *
10118     * <p>Note that text insertion mode is not started when a TextView receives focus and the
10119     * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
10120     */
10121    public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
10122        createEditorIfNeeded();
10123        mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
10124    }
10125
10126    /**
10127     * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
10128     *
10129     * @return The current custom insertion callback.
10130     */
10131    public ActionMode.Callback getCustomInsertionActionModeCallback() {
10132        return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
10133    }
10134
10135    private TextAssistant mTextAssistant;
10136
10137    /**
10138     * Sets the {@link TextAssistant} for this TextView.
10139     * If null, this TextView uses the default TextAssistant which comes from the Activity.
10140     */
10141    public void setTextAssistant(TextAssistant textAssistant) {
10142        mTextAssistant = textAssistant;
10143    }
10144
10145    /**
10146     * Returns the {@link TextAssistant} used by this TextView.
10147     * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or
10148     * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant.
10149     */
10150    public TextAssistant getTextAssistant() {
10151        if (mTextAssistant != null) {
10152            return mTextAssistant;
10153        }
10154        if (mContext instanceof Activity) {
10155            mTextAssistant = ((Activity) mContext).getTextAssistant();
10156        } else {
10157            // The context of this TextView should be an Activity. If it is not and no
10158            // text assistant has been set, return the TextClassificationManager.
10159            mTextAssistant = mContext.getSystemService(TextClassificationManager.class);
10160        }
10161        return  mTextAssistant;
10162    }
10163
10164    /**
10165     * @hide
10166     */
10167    protected void stopTextActionMode() {
10168        if (mEditor != null) {
10169            mEditor.stopTextActionMode();
10170        }
10171    }
10172
10173    boolean canUndo() {
10174        return mEditor != null && mEditor.canUndo();
10175    }
10176
10177    boolean canRedo() {
10178        return mEditor != null && mEditor.canRedo();
10179    }
10180
10181    boolean canCut() {
10182        if (hasPasswordTransformationMethod()) {
10183            return false;
10184        }
10185
10186        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
10187                && mEditor.mKeyListener != null) {
10188            return true;
10189        }
10190
10191        return false;
10192    }
10193
10194    boolean canCopy() {
10195        if (hasPasswordTransformationMethod()) {
10196            return false;
10197        }
10198
10199        if (mText.length() > 0 && hasSelection() && mEditor != null) {
10200            return true;
10201        }
10202
10203        return false;
10204    }
10205
10206    boolean canShare() {
10207        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
10208            return false;
10209        }
10210        return canCopy();
10211    }
10212
10213    boolean isDeviceProvisioned() {
10214        if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
10215            mDeviceProvisionedState = Settings.Global.getInt(
10216                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
10217                    ? DEVICE_PROVISIONED_YES
10218                    : DEVICE_PROVISIONED_NO;
10219        }
10220        return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
10221    }
10222
10223    boolean canPaste() {
10224        return (mText instanceof Editable
10225                && mEditor != null && mEditor.mKeyListener != null
10226                && getSelectionStart() >= 0
10227                && getSelectionEnd() >= 0
10228                && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
10229                        .hasPrimaryClip());
10230    }
10231
10232    boolean canProcessText() {
10233        if (getId() == View.NO_ID) {
10234            return false;
10235        }
10236        return canShare();
10237    }
10238
10239    boolean canSelectAllText() {
10240        return canSelectText() && !hasPasswordTransformationMethod()
10241                && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
10242    }
10243
10244    boolean selectAllText() {
10245        final int length = mText.length();
10246        Selection.setSelection((Spannable) mText, 0, length);
10247        return length > 0;
10248    }
10249
10250    void replaceSelectionWithText(CharSequence text) {
10251        ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
10252    }
10253
10254    /**
10255     * Paste clipboard content between min and max positions.
10256     */
10257    private void paste(int min, int max, boolean withFormatting) {
10258        ClipboardManager clipboard =
10259                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
10260        ClipData clip = clipboard.getPrimaryClip();
10261        if (clip != null) {
10262            boolean didFirst = false;
10263            for (int i = 0; i < clip.getItemCount(); i++) {
10264                final CharSequence paste;
10265                if (withFormatting) {
10266                    paste = clip.getItemAt(i).coerceToStyledText(getContext());
10267                } else {
10268                    // Get an item as text and remove all spans by toString().
10269                    final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
10270                    paste = (text instanceof Spanned) ? text.toString() : text;
10271                }
10272                if (paste != null) {
10273                    if (!didFirst) {
10274                        Selection.setSelection((Spannable) mText, max);
10275                        ((Editable) mText).replace(min, max, paste);
10276                        didFirst = true;
10277                    } else {
10278                        ((Editable) mText).insert(getSelectionEnd(), "\n");
10279                        ((Editable) mText).insert(getSelectionEnd(), paste);
10280                    }
10281                }
10282            }
10283            sLastCutCopyOrTextChangedTime = 0;
10284        }
10285    }
10286
10287    private void shareSelectedText() {
10288        String selectedText = getSelectedText();
10289        if (selectedText != null && !selectedText.isEmpty()) {
10290            Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
10291            sharingIntent.setType("text/plain");
10292            sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
10293            sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
10294            getContext().startActivity(Intent.createChooser(sharingIntent, null));
10295            Selection.setSelection((Spannable) mText, getSelectionEnd());
10296        }
10297    }
10298
10299    private void setPrimaryClip(ClipData clip) {
10300        ClipboardManager clipboard =
10301                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
10302        clipboard.setPrimaryClip(clip);
10303        sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
10304    }
10305
10306    /**
10307     * Get the character offset closest to the specified absolute position. A typical use case is to
10308     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
10309     *
10310     * @param x The horizontal absolute position of a point on screen
10311     * @param y The vertical absolute position of a point on screen
10312     * @return the character offset for the character whose position is closest to the specified
10313     *  position. Returns -1 if there is no layout.
10314     */
10315    public int getOffsetForPosition(float x, float y) {
10316        if (getLayout() == null) return -1;
10317        final int line = getLineAtCoordinate(y);
10318        final int offset = getOffsetAtCoordinate(line, x);
10319        return offset;
10320    }
10321
10322    float convertToLocalHorizontalCoordinate(float x) {
10323        x -= getTotalPaddingLeft();
10324        // Clamp the position to inside of the view.
10325        x = Math.max(0.0f, x);
10326        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
10327        x += getScrollX();
10328        return x;
10329    }
10330
10331    int getLineAtCoordinate(float y) {
10332        y -= getTotalPaddingTop();
10333        // Clamp the position to inside of the view.
10334        y = Math.max(0.0f, y);
10335        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
10336        y += getScrollY();
10337        return getLayout().getLineForVertical((int) y);
10338    }
10339
10340    int getLineAtCoordinateUnclamped(float y) {
10341        y -= getTotalPaddingTop();
10342        y += getScrollY();
10343        return getLayout().getLineForVertical((int) y);
10344    }
10345
10346    int getOffsetAtCoordinate(int line, float x) {
10347        x = convertToLocalHorizontalCoordinate(x);
10348        return getLayout().getOffsetForHorizontal(line, x);
10349    }
10350
10351    @Override
10352    public boolean onDragEvent(DragEvent event) {
10353        switch (event.getAction()) {
10354            case DragEvent.ACTION_DRAG_STARTED:
10355                return mEditor != null && mEditor.hasInsertionController();
10356
10357            case DragEvent.ACTION_DRAG_ENTERED:
10358                TextView.this.requestFocus();
10359                return true;
10360
10361            case DragEvent.ACTION_DRAG_LOCATION:
10362                final int offset = getOffsetForPosition(event.getX(), event.getY());
10363                Selection.setSelection((Spannable) mText, offset);
10364                return true;
10365
10366            case DragEvent.ACTION_DROP:
10367                if (mEditor != null) mEditor.onDrop(event);
10368                return true;
10369
10370            case DragEvent.ACTION_DRAG_ENDED:
10371            case DragEvent.ACTION_DRAG_EXITED:
10372            default:
10373                return true;
10374        }
10375    }
10376
10377    boolean isInBatchEditMode() {
10378        if (mEditor == null) return false;
10379        final Editor.InputMethodState ims = mEditor.mInputMethodState;
10380        if (ims != null) {
10381            return ims.mBatchEditNesting > 0;
10382        }
10383        return mEditor.mInBatchEditControllers;
10384    }
10385
10386    @Override
10387    public void onRtlPropertiesChanged(int layoutDirection) {
10388        super.onRtlPropertiesChanged(layoutDirection);
10389
10390        final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
10391        if (mTextDir != newTextDir) {
10392            mTextDir = newTextDir;
10393            if (mLayout != null) {
10394                checkForRelayout();
10395            }
10396        }
10397    }
10398
10399    /**
10400     * @hide
10401     */
10402    protected TextDirectionHeuristic getTextDirectionHeuristic() {
10403        if (hasPasswordTransformationMethod()) {
10404            // passwords fields should be LTR
10405            return TextDirectionHeuristics.LTR;
10406        }
10407
10408        // Always need to resolve layout direction first
10409        final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
10410
10411        // Now, we can select the heuristic
10412        switch (getTextDirection()) {
10413            default:
10414            case TEXT_DIRECTION_FIRST_STRONG:
10415                return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
10416                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
10417            case TEXT_DIRECTION_ANY_RTL:
10418                return TextDirectionHeuristics.ANYRTL_LTR;
10419            case TEXT_DIRECTION_LTR:
10420                return TextDirectionHeuristics.LTR;
10421            case TEXT_DIRECTION_RTL:
10422                return TextDirectionHeuristics.RTL;
10423            case TEXT_DIRECTION_LOCALE:
10424                return TextDirectionHeuristics.LOCALE;
10425            case TEXT_DIRECTION_FIRST_STRONG_LTR:
10426                return TextDirectionHeuristics.FIRSTSTRONG_LTR;
10427            case TEXT_DIRECTION_FIRST_STRONG_RTL:
10428                return TextDirectionHeuristics.FIRSTSTRONG_RTL;
10429        }
10430    }
10431
10432    /**
10433     * @hide
10434     */
10435    @Override
10436    public void onResolveDrawables(int layoutDirection) {
10437        // No need to resolve twice
10438        if (mLastLayoutDirection == layoutDirection) {
10439            return;
10440        }
10441        mLastLayoutDirection = layoutDirection;
10442
10443        // Resolve drawables
10444        if (mDrawables != null) {
10445            if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
10446                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
10447                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
10448                applyCompoundDrawableTint();
10449            }
10450        }
10451    }
10452
10453    /**
10454     * Prepares a drawable for display by propagating layout direction and
10455     * drawable state.
10456     *
10457     * @param dr the drawable to prepare
10458     */
10459    private void prepareDrawableForDisplay(@Nullable Drawable dr) {
10460        if (dr == null) {
10461            return;
10462        }
10463
10464        dr.setLayoutDirection(getLayoutDirection());
10465
10466        if (dr.isStateful()) {
10467            dr.setState(getDrawableState());
10468            dr.jumpToCurrentState();
10469        }
10470    }
10471
10472    /**
10473     * @hide
10474     */
10475    protected void resetResolvedDrawables() {
10476        super.resetResolvedDrawables();
10477        mLastLayoutDirection = -1;
10478    }
10479
10480    /**
10481     * @hide
10482     */
10483    protected void viewClicked(InputMethodManager imm) {
10484        if (imm != null) {
10485            imm.viewClicked(this);
10486        }
10487    }
10488
10489    /**
10490     * Deletes the range of text [start, end[.
10491     * @hide
10492     */
10493    protected void deleteText_internal(int start, int end) {
10494        ((Editable) mText).delete(start, end);
10495    }
10496
10497    /**
10498     * Replaces the range of text [start, end[ by replacement text
10499     * @hide
10500     */
10501    protected void replaceText_internal(int start, int end, CharSequence text) {
10502        ((Editable) mText).replace(start, end, text);
10503    }
10504
10505    /**
10506     * Sets a span on the specified range of text
10507     * @hide
10508     */
10509    protected void setSpan_internal(Object span, int start, int end, int flags) {
10510        ((Editable) mText).setSpan(span, start, end, flags);
10511    }
10512
10513    /**
10514     * Moves the cursor to the specified offset position in text
10515     * @hide
10516     */
10517    protected void setCursorPosition_internal(int start, int end) {
10518        Selection.setSelection(((Editable) mText), start, end);
10519    }
10520
10521    /**
10522     * An Editor should be created as soon as any of the editable-specific fields (grouped
10523     * inside the Editor object) is assigned to a non-default value.
10524     * This method will create the Editor if needed.
10525     *
10526     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
10527     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
10528     * Editor for backward compatibility, as soon as one of these fields is assigned.
10529     *
10530     * Also note that for performance reasons, the mEditor is created when needed, but not
10531     * reset when no more edit-specific fields are needed.
10532     */
10533    private void createEditorIfNeeded() {
10534        if (mEditor == null) {
10535            mEditor = new Editor(this);
10536        }
10537    }
10538
10539    /**
10540     * @hide
10541     */
10542    @Override
10543    public CharSequence getIterableTextForAccessibility() {
10544        return mText;
10545    }
10546
10547    private void ensureIterableTextForAccessibilitySelectable() {
10548        if (!(mText instanceof Spannable)) {
10549            setText(mText, BufferType.SPANNABLE);
10550        }
10551    }
10552
10553    /**
10554     * @hide
10555     */
10556    @Override
10557    public TextSegmentIterator getIteratorForGranularity(int granularity) {
10558        switch (granularity) {
10559            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
10560                Spannable text = (Spannable) getIterableTextForAccessibility();
10561                if (!TextUtils.isEmpty(text) && getLayout() != null) {
10562                    AccessibilityIterators.LineTextSegmentIterator iterator =
10563                            AccessibilityIterators.LineTextSegmentIterator.getInstance();
10564                    iterator.initialize(text, getLayout());
10565                    return iterator;
10566                }
10567            } break;
10568            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
10569                Spannable text = (Spannable) getIterableTextForAccessibility();
10570                if (!TextUtils.isEmpty(text) && getLayout() != null) {
10571                    AccessibilityIterators.PageTextSegmentIterator iterator =
10572                            AccessibilityIterators.PageTextSegmentIterator.getInstance();
10573                    iterator.initialize(this);
10574                    return iterator;
10575                }
10576            } break;
10577        }
10578        return super.getIteratorForGranularity(granularity);
10579    }
10580
10581    /**
10582     * @hide
10583     */
10584    @Override
10585    public int getAccessibilitySelectionStart() {
10586        return getSelectionStart();
10587    }
10588
10589    /**
10590     * @hide
10591     */
10592    public boolean isAccessibilitySelectionExtendable() {
10593        return true;
10594    }
10595
10596    /**
10597     * @hide
10598     */
10599    @Override
10600    public int getAccessibilitySelectionEnd() {
10601        return getSelectionEnd();
10602    }
10603
10604    /**
10605     * @hide
10606     */
10607    @Override
10608    public void setAccessibilitySelection(int start, int end) {
10609        if (getAccessibilitySelectionStart() == start
10610                && getAccessibilitySelectionEnd() == end) {
10611            return;
10612        }
10613        CharSequence text = getIterableTextForAccessibility();
10614        if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
10615            Selection.setSelection((Spannable) text, start, end);
10616        } else {
10617            Selection.removeSelection((Spannable) text);
10618        }
10619        // Hide all selection controllers used for adjusting selection
10620        // since we are doing so explicitlty by other means and these
10621        // controllers interact with how selection behaves.
10622        if (mEditor != null) {
10623            mEditor.hideCursorAndSpanControllers();
10624            mEditor.stopTextActionMode();
10625        }
10626    }
10627
10628    /** @hide */
10629    @Override
10630    protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
10631        super.encodeProperties(stream);
10632
10633        TruncateAt ellipsize = getEllipsize();
10634        stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
10635        stream.addProperty("text:textSize", getTextSize());
10636        stream.addProperty("text:scaledTextSize", getScaledTextSize());
10637        stream.addProperty("text:typefaceStyle", getTypefaceStyle());
10638        stream.addProperty("text:selectionStart", getSelectionStart());
10639        stream.addProperty("text:selectionEnd", getSelectionEnd());
10640        stream.addProperty("text:curTextColor", mCurTextColor);
10641        stream.addProperty("text:text", mText == null ? null : mText.toString());
10642        stream.addProperty("text:gravity", mGravity);
10643    }
10644
10645    /**
10646     * User interface state that is stored by TextView for implementing
10647     * {@link View#onSaveInstanceState}.
10648     */
10649    public static class SavedState extends BaseSavedState {
10650        int selStart = -1;
10651        int selEnd = -1;
10652        CharSequence text;
10653        boolean frozenWithFocus;
10654        CharSequence error;
10655        ParcelableParcel editorState;  // Optional state from Editor.
10656
10657        SavedState(Parcelable superState) {
10658            super(superState);
10659        }
10660
10661        @Override
10662        public void writeToParcel(Parcel out, int flags) {
10663            super.writeToParcel(out, flags);
10664            out.writeInt(selStart);
10665            out.writeInt(selEnd);
10666            out.writeInt(frozenWithFocus ? 1 : 0);
10667            TextUtils.writeToParcel(text, out, flags);
10668
10669            if (error == null) {
10670                out.writeInt(0);
10671            } else {
10672                out.writeInt(1);
10673                TextUtils.writeToParcel(error, out, flags);
10674            }
10675
10676            if (editorState == null) {
10677                out.writeInt(0);
10678            } else {
10679                out.writeInt(1);
10680                editorState.writeToParcel(out, flags);
10681            }
10682        }
10683
10684        @Override
10685        public String toString() {
10686            String str = "TextView.SavedState{"
10687                    + Integer.toHexString(System.identityHashCode(this))
10688                    + " start=" + selStart + " end=" + selEnd;
10689            if (text != null) {
10690                str += " text=" + text;
10691            }
10692            return str + "}";
10693        }
10694
10695        @SuppressWarnings("hiding")
10696        public static final Parcelable.Creator<SavedState> CREATOR =
10697                new Parcelable.Creator<SavedState>() {
10698                    public SavedState createFromParcel(Parcel in) {
10699                        return new SavedState(in);
10700                    }
10701
10702                    public SavedState[] newArray(int size) {
10703                        return new SavedState[size];
10704                    }
10705                };
10706
10707        private SavedState(Parcel in) {
10708            super(in);
10709            selStart = in.readInt();
10710            selEnd = in.readInt();
10711            frozenWithFocus = (in.readInt() != 0);
10712            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10713
10714            if (in.readInt() != 0) {
10715                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10716            }
10717
10718            if (in.readInt() != 0) {
10719                editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10720            }
10721        }
10722    }
10723
10724    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10725        private char[] mChars;
10726        private int mStart, mLength;
10727
10728        public CharWrapper(char[] chars, int start, int len) {
10729            mChars = chars;
10730            mStart = start;
10731            mLength = len;
10732        }
10733
10734        /* package */ void set(char[] chars, int start, int len) {
10735            mChars = chars;
10736            mStart = start;
10737            mLength = len;
10738        }
10739
10740        public int length() {
10741            return mLength;
10742        }
10743
10744        public char charAt(int off) {
10745            return mChars[off + mStart];
10746        }
10747
10748        @Override
10749        public String toString() {
10750            return new String(mChars, mStart, mLength);
10751        }
10752
10753        public CharSequence subSequence(int start, int end) {
10754            if (start < 0 || end < 0 || start > mLength || end > mLength) {
10755                throw new IndexOutOfBoundsException(start + ", " + end);
10756            }
10757
10758            return new String(mChars, start + mStart, end - start);
10759        }
10760
10761        public void getChars(int start, int end, char[] buf, int off) {
10762            if (start < 0 || end < 0 || start > mLength || end > mLength) {
10763                throw new IndexOutOfBoundsException(start + ", " + end);
10764            }
10765
10766            System.arraycopy(mChars, start + mStart, buf, off, end - start);
10767        }
10768
10769        @Override
10770        public void drawText(BaseCanvas c, int start, int end,
10771                             float x, float y, Paint p) {
10772            c.drawText(mChars, start + mStart, end - start, x, y, p);
10773        }
10774
10775        @Override
10776        public void drawTextRun(BaseCanvas c, int start, int end,
10777                int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
10778            int count = end - start;
10779            int contextCount = contextEnd - contextStart;
10780            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
10781                    contextCount, x, y, isRtl, p);
10782        }
10783
10784        public float measureText(int start, int end, Paint p) {
10785            return p.measureText(mChars, start + mStart, end - start);
10786        }
10787
10788        public int getTextWidths(int start, int end, float[] widths, Paint p) {
10789            return p.getTextWidths(mChars, start + mStart, end - start, widths);
10790        }
10791
10792        public float getTextRunAdvances(int start, int end, int contextStart,
10793                int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
10794                Paint p) {
10795            int count = end - start;
10796            int contextCount = contextEnd - contextStart;
10797            return p.getTextRunAdvances(mChars, start + mStart, count,
10798                    contextStart + mStart, contextCount, isRtl, advances,
10799                    advancesIndex);
10800        }
10801
10802        public int getTextRunCursor(int contextStart, int contextEnd, int dir,
10803                int offset, int cursorOpt, Paint p) {
10804            int contextCount = contextEnd - contextStart;
10805            return p.getTextRunCursor(mChars, contextStart + mStart,
10806                    contextCount, dir, offset + mStart, cursorOpt);
10807        }
10808    }
10809
10810    private static final class Marquee {
10811        // TODO: Add an option to configure this
10812        private static final float MARQUEE_DELTA_MAX = 0.07f;
10813        private static final int MARQUEE_DELAY = 1200;
10814        private static final int MARQUEE_DP_PER_SECOND = 30;
10815
10816        private static final byte MARQUEE_STOPPED = 0x0;
10817        private static final byte MARQUEE_STARTING = 0x1;
10818        private static final byte MARQUEE_RUNNING = 0x2;
10819
10820        private final WeakReference<TextView> mView;
10821        private final Choreographer mChoreographer;
10822
10823        private byte mStatus = MARQUEE_STOPPED;
10824        private final float mPixelsPerSecond;
10825        private float mMaxScroll;
10826        private float mMaxFadeScroll;
10827        private float mGhostStart;
10828        private float mGhostOffset;
10829        private float mFadeStop;
10830        private int mRepeatLimit;
10831
10832        private float mScroll;
10833        private long mLastAnimationMs;
10834
10835        Marquee(TextView v) {
10836            final float density = v.getContext().getResources().getDisplayMetrics().density;
10837            mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
10838            mView = new WeakReference<TextView>(v);
10839            mChoreographer = Choreographer.getInstance();
10840        }
10841
10842        private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10843            @Override
10844            public void doFrame(long frameTimeNanos) {
10845                tick();
10846            }
10847        };
10848
10849        private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10850            @Override
10851            public void doFrame(long frameTimeNanos) {
10852                mStatus = MARQUEE_RUNNING;
10853                mLastAnimationMs = mChoreographer.getFrameTime();
10854                tick();
10855            }
10856        };
10857
10858        private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10859            @Override
10860            public void doFrame(long frameTimeNanos) {
10861                if (mStatus == MARQUEE_RUNNING) {
10862                    if (mRepeatLimit >= 0) {
10863                        mRepeatLimit--;
10864                    }
10865                    start(mRepeatLimit);
10866                }
10867            }
10868        };
10869
10870        void tick() {
10871            if (mStatus != MARQUEE_RUNNING) {
10872                return;
10873            }
10874
10875            mChoreographer.removeFrameCallback(mTickCallback);
10876
10877            final TextView textView = mView.get();
10878            if (textView != null && (textView.isFocused() || textView.isSelected())) {
10879                long currentMs = mChoreographer.getFrameTime();
10880                long deltaMs = currentMs - mLastAnimationMs;
10881                mLastAnimationMs = currentMs;
10882                float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10883                mScroll += deltaPx;
10884                if (mScroll > mMaxScroll) {
10885                    mScroll = mMaxScroll;
10886                    mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
10887                } else {
10888                    mChoreographer.postFrameCallback(mTickCallback);
10889                }
10890                textView.invalidate();
10891            }
10892        }
10893
10894        void stop() {
10895            mStatus = MARQUEE_STOPPED;
10896            mChoreographer.removeFrameCallback(mStartCallback);
10897            mChoreographer.removeFrameCallback(mRestartCallback);
10898            mChoreographer.removeFrameCallback(mTickCallback);
10899            resetScroll();
10900        }
10901
10902        private void resetScroll() {
10903            mScroll = 0.0f;
10904            final TextView textView = mView.get();
10905            if (textView != null) textView.invalidate();
10906        }
10907
10908        void start(int repeatLimit) {
10909            if (repeatLimit == 0) {
10910                stop();
10911                return;
10912            }
10913            mRepeatLimit = repeatLimit;
10914            final TextView textView = mView.get();
10915            if (textView != null && textView.mLayout != null) {
10916                mStatus = MARQUEE_STARTING;
10917                mScroll = 0.0f;
10918                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
10919                        - textView.getCompoundPaddingRight();
10920                final float lineWidth = textView.mLayout.getLineWidth(0);
10921                final float gap = textWidth / 3.0f;
10922                mGhostStart = lineWidth - textWidth + gap;
10923                mMaxScroll = mGhostStart + textWidth;
10924                mGhostOffset = lineWidth + gap;
10925                mFadeStop = lineWidth + textWidth / 6.0f;
10926                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10927
10928                textView.invalidate();
10929                mChoreographer.postFrameCallback(mStartCallback);
10930            }
10931        }
10932
10933        float getGhostOffset() {
10934            return mGhostOffset;
10935        }
10936
10937        float getScroll() {
10938            return mScroll;
10939        }
10940
10941        float getMaxFadeScroll() {
10942            return mMaxFadeScroll;
10943        }
10944
10945        boolean shouldDrawLeftFade() {
10946            return mScroll <= mFadeStop;
10947        }
10948
10949        boolean shouldDrawGhost() {
10950            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10951        }
10952
10953        boolean isRunning() {
10954            return mStatus == MARQUEE_RUNNING;
10955        }
10956
10957        boolean isStopped() {
10958            return mStatus == MARQUEE_STOPPED;
10959        }
10960    }
10961
10962    private class ChangeWatcher implements TextWatcher, SpanWatcher {
10963
10964        private CharSequence mBeforeText;
10965
10966        public void beforeTextChanged(CharSequence buffer, int start,
10967                                      int before, int after) {
10968            if (DEBUG_EXTRACT) {
10969                Log.v(LOG_TAG, "beforeTextChanged start=" + start
10970                        + " before=" + before + " after=" + after + ": " + buffer);
10971            }
10972
10973            if (AccessibilityManager.getInstance(mContext).isEnabled()
10974                    && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10975                            || shouldSpeakPasswordsForAccessibility())) {
10976                mBeforeText = buffer.toString();
10977            }
10978
10979            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10980        }
10981
10982        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10983            if (DEBUG_EXTRACT) {
10984                Log.v(LOG_TAG, "onTextChanged start=" + start
10985                        + " before=" + before + " after=" + after + ": " + buffer);
10986            }
10987            TextView.this.handleTextChanged(buffer, start, before, after);
10988
10989            if (AccessibilityManager.getInstance(mContext).isEnabled()
10990                    && (isFocused() || isSelected() && isShown())) {
10991                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10992                mBeforeText = null;
10993            }
10994        }
10995
10996        public void afterTextChanged(Editable buffer) {
10997            if (DEBUG_EXTRACT) {
10998                Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10999            }
11000            TextView.this.sendAfterTextChanged(buffer);
11001
11002            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
11003                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
11004            }
11005        }
11006
11007        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
11008            if (DEBUG_EXTRACT) {
11009                Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
11010                        + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
11011            }
11012            TextView.this.spanChange(buf, what, s, st, e, en);
11013        }
11014
11015        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
11016            if (DEBUG_EXTRACT) {
11017                Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
11018            }
11019            TextView.this.spanChange(buf, what, -1, s, -1, e);
11020        }
11021
11022        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
11023            if (DEBUG_EXTRACT) {
11024                Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
11025            }
11026            TextView.this.spanChange(buf, what, s, -1, e, -1);
11027        }
11028    }
11029}
11030