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