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