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