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