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