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