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