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