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