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