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