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