TextView.java revision e62beb5c20c18beec5bee5ff0a9d84067949c36d
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.ClipData.Item;
22import android.content.ClipboardManager;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.content.res.ColorStateList;
27import android.content.res.CompatibilityInfo;
28import android.content.res.Resources;
29import android.content.res.TypedArray;
30import android.content.res.XmlResourceParser;
31import android.graphics.Canvas;
32import android.graphics.Color;
33import android.graphics.Paint;
34import android.graphics.Path;
35import android.graphics.Rect;
36import android.graphics.RectF;
37import android.graphics.Typeface;
38import android.graphics.drawable.Drawable;
39import android.inputmethodservice.ExtractEditText;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.Message;
43import android.os.Parcel;
44import android.os.Parcelable;
45import android.os.SystemClock;
46import android.provider.Settings;
47import android.text.BoringLayout;
48import android.text.DynamicLayout;
49import android.text.Editable;
50import android.text.GetChars;
51import android.text.GraphicsOperations;
52import android.text.InputFilter;
53import android.text.InputType;
54import android.text.Layout;
55import android.text.ParcelableSpan;
56import android.text.Selection;
57import android.text.SpanWatcher;
58import android.text.Spannable;
59import android.text.SpannableString;
60import android.text.SpannableStringBuilder;
61import android.text.Spanned;
62import android.text.SpannedString;
63import android.text.StaticLayout;
64import android.text.TextDirectionHeuristic;
65import android.text.TextDirectionHeuristics;
66import android.text.TextPaint;
67import android.text.TextUtils;
68import android.text.TextUtils.TruncateAt;
69import android.text.TextWatcher;
70import android.text.method.AllCapsTransformationMethod;
71import android.text.method.ArrowKeyMovementMethod;
72import android.text.method.DateKeyListener;
73import android.text.method.DateTimeKeyListener;
74import android.text.method.DialerKeyListener;
75import android.text.method.DigitsKeyListener;
76import android.text.method.KeyListener;
77import android.text.method.LinkMovementMethod;
78import android.text.method.MetaKeyKeyListener;
79import android.text.method.MovementMethod;
80import android.text.method.PasswordTransformationMethod;
81import android.text.method.SingleLineTransformationMethod;
82import android.text.method.TextKeyListener;
83import android.text.method.TimeKeyListener;
84import android.text.method.TransformationMethod;
85import android.text.method.TransformationMethod2;
86import android.text.method.WordIterator;
87import android.text.style.CharacterStyle;
88import android.text.style.ClickableSpan;
89import android.text.style.EasyEditSpan;
90import android.text.style.ParagraphStyle;
91import android.text.style.SpellCheckSpan;
92import android.text.style.SuggestionRangeSpan;
93import android.text.style.SuggestionSpan;
94import android.text.style.TextAppearanceSpan;
95import android.text.style.URLSpan;
96import android.text.style.UpdateAppearance;
97import android.text.util.Linkify;
98import android.util.AttributeSet;
99import android.util.DisplayMetrics;
100import android.util.FloatMath;
101import android.util.Log;
102import android.util.TypedValue;
103import android.view.ActionMode;
104import android.view.ActionMode.Callback;
105import android.view.DisplayList;
106import android.view.DragEvent;
107import android.view.Gravity;
108import android.view.HapticFeedbackConstants;
109import android.view.HardwareCanvas;
110import android.view.KeyCharacterMap;
111import android.view.KeyEvent;
112import android.view.LayoutInflater;
113import android.view.Menu;
114import android.view.MenuItem;
115import android.view.MotionEvent;
116import android.view.View;
117import android.view.ViewConfiguration;
118import android.view.ViewDebug;
119import android.view.ViewGroup;
120import android.view.ViewGroup.LayoutParams;
121import android.view.ViewParent;
122import android.view.ViewRootImpl;
123import android.view.ViewTreeObserver;
124import android.view.WindowManager;
125import android.view.accessibility.AccessibilityEvent;
126import android.view.accessibility.AccessibilityManager;
127import android.view.accessibility.AccessibilityNodeInfo;
128import android.view.animation.AnimationUtils;
129import android.view.inputmethod.BaseInputConnection;
130import android.view.inputmethod.CompletionInfo;
131import android.view.inputmethod.CorrectionInfo;
132import android.view.inputmethod.EditorInfo;
133import android.view.inputmethod.ExtractedText;
134import android.view.inputmethod.ExtractedTextRequest;
135import android.view.inputmethod.InputConnection;
136import android.view.inputmethod.InputMethodManager;
137import android.view.textservice.SpellCheckerSubtype;
138import android.view.textservice.TextServicesManager;
139import android.widget.AdapterView.OnItemClickListener;
140import android.widget.RemoteViews.RemoteView;
141
142import com.android.internal.util.FastMath;
143import com.android.internal.widget.EditableInputConnection;
144
145import org.xmlpull.v1.XmlPullParserException;
146
147import java.io.IOException;
148import java.lang.ref.WeakReference;
149import java.text.BreakIterator;
150import java.util.ArrayList;
151import java.util.Arrays;
152import java.util.Comparator;
153import java.util.HashMap;
154import java.util.Locale;
155
156/**
157 * Displays text to the user and optionally allows them to edit it.  A TextView
158 * is a complete text editor, however the basic class is configured to not
159 * allow editing; see {@link EditText} for a subclass that configures the text
160 * view for editing.
161 *
162 * <p>
163 * <b>XML attributes</b>
164 * <p>
165 * See {@link android.R.styleable#TextView TextView Attributes},
166 * {@link android.R.styleable#View View Attributes}
167 *
168 * @attr ref android.R.styleable#TextView_text
169 * @attr ref android.R.styleable#TextView_bufferType
170 * @attr ref android.R.styleable#TextView_hint
171 * @attr ref android.R.styleable#TextView_textColor
172 * @attr ref android.R.styleable#TextView_textColorHighlight
173 * @attr ref android.R.styleable#TextView_textColorHint
174 * @attr ref android.R.styleable#TextView_textAppearance
175 * @attr ref android.R.styleable#TextView_textColorLink
176 * @attr ref android.R.styleable#TextView_textSize
177 * @attr ref android.R.styleable#TextView_textScaleX
178 * @attr ref android.R.styleable#TextView_typeface
179 * @attr ref android.R.styleable#TextView_textStyle
180 * @attr ref android.R.styleable#TextView_cursorVisible
181 * @attr ref android.R.styleable#TextView_maxLines
182 * @attr ref android.R.styleable#TextView_maxHeight
183 * @attr ref android.R.styleable#TextView_lines
184 * @attr ref android.R.styleable#TextView_height
185 * @attr ref android.R.styleable#TextView_minLines
186 * @attr ref android.R.styleable#TextView_minHeight
187 * @attr ref android.R.styleable#TextView_maxEms
188 * @attr ref android.R.styleable#TextView_maxWidth
189 * @attr ref android.R.styleable#TextView_ems
190 * @attr ref android.R.styleable#TextView_width
191 * @attr ref android.R.styleable#TextView_minEms
192 * @attr ref android.R.styleable#TextView_minWidth
193 * @attr ref android.R.styleable#TextView_gravity
194 * @attr ref android.R.styleable#TextView_scrollHorizontally
195 * @attr ref android.R.styleable#TextView_password
196 * @attr ref android.R.styleable#TextView_singleLine
197 * @attr ref android.R.styleable#TextView_selectAllOnFocus
198 * @attr ref android.R.styleable#TextView_includeFontPadding
199 * @attr ref android.R.styleable#TextView_maxLength
200 * @attr ref android.R.styleable#TextView_shadowColor
201 * @attr ref android.R.styleable#TextView_shadowDx
202 * @attr ref android.R.styleable#TextView_shadowDy
203 * @attr ref android.R.styleable#TextView_shadowRadius
204 * @attr ref android.R.styleable#TextView_autoLink
205 * @attr ref android.R.styleable#TextView_linksClickable
206 * @attr ref android.R.styleable#TextView_numeric
207 * @attr ref android.R.styleable#TextView_digits
208 * @attr ref android.R.styleable#TextView_phoneNumber
209 * @attr ref android.R.styleable#TextView_inputMethod
210 * @attr ref android.R.styleable#TextView_capitalize
211 * @attr ref android.R.styleable#TextView_autoText
212 * @attr ref android.R.styleable#TextView_editable
213 * @attr ref android.R.styleable#TextView_freezesText
214 * @attr ref android.R.styleable#TextView_ellipsize
215 * @attr ref android.R.styleable#TextView_drawableTop
216 * @attr ref android.R.styleable#TextView_drawableBottom
217 * @attr ref android.R.styleable#TextView_drawableRight
218 * @attr ref android.R.styleable#TextView_drawableLeft
219 * @attr ref android.R.styleable#TextView_drawablePadding
220 * @attr ref android.R.styleable#TextView_lineSpacingExtra
221 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
222 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
223 * @attr ref android.R.styleable#TextView_inputType
224 * @attr ref android.R.styleable#TextView_imeOptions
225 * @attr ref android.R.styleable#TextView_privateImeOptions
226 * @attr ref android.R.styleable#TextView_imeActionLabel
227 * @attr ref android.R.styleable#TextView_imeActionId
228 * @attr ref android.R.styleable#TextView_editorExtras
229 */
230@RemoteView
231public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
232    static final String LOG_TAG = "TextView";
233    static final boolean DEBUG_EXTRACT = false;
234
235    private static final int PRIORITY = 100;
236    private int mCurrentAlpha = 255;
237
238    final int[] mTempCoords = new int[2];
239    Rect mTempRect;
240
241    private ColorStateList mTextColor;
242    private int mCurTextColor;
243    private ColorStateList mHintTextColor;
244    private ColorStateList mLinkTextColor;
245    private int mCurHintTextColor;
246    private boolean mFreezesText;
247    private boolean mFrozenWithFocus;
248    private boolean mTemporaryDetach;
249    private boolean mDispatchTemporaryDetach;
250
251    private boolean mDiscardNextActionUp = false;
252    private boolean mIgnoreActionUpEvent = false;
253
254    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
255    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
256
257    private float mShadowRadius, mShadowDx, mShadowDy;
258
259    private boolean mPreDrawRegistered;
260
261    private TextUtils.TruncateAt mEllipsize = null;
262
263    // Enum for the "typeface" XML parameter.
264    // TODO: How can we get this from the XML instead of hardcoding it here?
265    private static final int SANS = 1;
266    private static final int SERIF = 2;
267    private static final int MONOSPACE = 3;
268
269    // Bitfield for the "numeric" XML parameter.
270    // TODO: How can we get this from the XML instead of hardcoding it here?
271    private static final int SIGNED = 2;
272    private static final int DECIMAL = 4;
273
274    static class Drawables {
275        final Rect mCompoundRect = new Rect();
276        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
277                mDrawableStart, mDrawableEnd;
278        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
279                mDrawableSizeStart, mDrawableSizeEnd;
280        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
281                mDrawableHeightStart, mDrawableHeightEnd;
282        int mDrawablePadding;
283    }
284    private Drawables mDrawables;
285
286    private DisplayList mTextDisplayList;
287    private boolean mTextDisplayListIsValid;
288
289    private CharSequence mError;
290    private boolean mErrorWasChanged;
291    private ErrorPopup mPopup;
292    /**
293     * This flag is set if the TextView tries to display an error before it
294     * is attached to the window (so its position is still unknown).
295     * It causes the error to be shown later, when onAttachedToWindow()
296     * is called.
297     */
298    private boolean mShowErrorAfterAttach;
299
300    private CharWrapper mCharWrapper = null;
301
302    private boolean mSelectionMoved = false;
303    private boolean mTouchFocusSelected = false;
304
305    private Marquee mMarquee;
306    private boolean mRestartMarquee;
307
308    private int mMarqueeRepeatLimit = 3;
309
310    static class InputContentType {
311        int imeOptions = EditorInfo.IME_NULL;
312        String privateImeOptions;
313        CharSequence imeActionLabel;
314        int imeActionId;
315        Bundle extras;
316        OnEditorActionListener onEditorActionListener;
317        boolean enterDown;
318    }
319    InputContentType mInputContentType;
320
321    static class InputMethodState {
322        Rect mCursorRectInWindow = new Rect();
323        RectF mTmpRectF = new RectF();
324        float[] mTmpOffset = new float[2];
325        ExtractedTextRequest mExtracting;
326        final ExtractedText mTmpExtracted = new ExtractedText();
327        int mBatchEditNesting;
328        boolean mCursorChanged;
329        boolean mSelectionModeChanged;
330        boolean mContentChanged;
331        int mChangedStart, mChangedEnd, mChangedDelta;
332    }
333    InputMethodState mInputMethodState;
334
335    private int mTextSelectHandleLeftRes;
336    private int mTextSelectHandleRightRes;
337    private int mTextSelectHandleRes;
338
339    private int mTextEditSuggestionItemLayout;
340    private SuggestionsPopupWindow mSuggestionsPopupWindow;
341    private SuggestionRangeSpan mSuggestionRangeSpan;
342    private Runnable mShowSuggestionRunnable;
343
344    private int mCursorDrawableRes;
345    private final Drawable[] mCursorDrawable = new Drawable[2];
346    private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2 (split)
347
348    private Drawable mSelectHandleLeft;
349    private Drawable mSelectHandleRight;
350    private Drawable mSelectHandleCenter;
351
352    // Global listener that detects changes in the global position of the TextView
353    private PositionListener mPositionListener;
354
355    private float mLastDownPositionX, mLastDownPositionY;
356    private Callback mCustomSelectionActionModeCallback;
357
358    // Set when this TextView gained focus with some text selected. Will start selection mode.
359    private boolean mCreatedWithASelection = false;
360
361    private WordIterator mWordIterator;
362
363    private SpellChecker mSpellChecker;
364
365    // The alignment to pass to Layout, or null if not resolved.
366    private Layout.Alignment mLayoutAlignment;
367
368    // The default value for mTextAlign.
369    private TextAlign mTextAlign = TextAlign.INHERIT;
370
371    private static enum TextAlign {
372        INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END;
373    }
374
375    private boolean mResolvedDrawables = false;
376
377    /**
378     * On some devices the fading edges add a performance penalty if used
379     * extensively in the same layout. This mode indicates how the marquee
380     * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
381     */
382    private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
383
384    /**
385     * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
386     * the layout that should be used when the mode switches.
387     */
388    private Layout mSavedMarqueeModeLayout;
389
390    /**
391     * Draw marquee text with fading edges as usual
392     */
393    private static final int MARQUEE_FADE_NORMAL = 0;
394
395    /**
396     * Draw marquee text as ellipsize end while inactive instead of with the fade.
397     * (Useful for devices where the fade can be expensive if overdone)
398     */
399    private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
400
401    /**
402     * Draw marquee text with fading edges because it is currently active/animating.
403     */
404    private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
405
406    /*
407     * Kick-start the font cache for the zygote process (to pay the cost of
408     * initializing freetype for our default font only once).
409     */
410    static {
411        Paint p = new Paint();
412        p.setAntiAlias(true);
413        // We don't care about the result, just the side-effect of measuring.
414        p.measureText("H");
415    }
416
417    /**
418     * Interface definition for a callback to be invoked when an action is
419     * performed on the editor.
420     */
421    public interface OnEditorActionListener {
422        /**
423         * Called when an action is being performed.
424         *
425         * @param v The view that was clicked.
426         * @param actionId Identifier of the action.  This will be either the
427         * identifier you supplied, or {@link EditorInfo#IME_NULL
428         * EditorInfo.IME_NULL} if being called due to the enter key
429         * being pressed.
430         * @param event If triggered by an enter key, this is the event;
431         * otherwise, this is null.
432         * @return Return true if you have consumed the action, else false.
433         */
434        boolean onEditorAction(TextView v, int actionId, KeyEvent event);
435    }
436
437    public TextView(Context context) {
438        this(context, null);
439    }
440
441    public TextView(Context context, AttributeSet attrs) {
442        this(context, attrs, com.android.internal.R.attr.textViewStyle);
443    }
444
445    @SuppressWarnings("deprecation")
446    public TextView(Context context, AttributeSet attrs, int defStyle) {
447        super(context, attrs, defStyle);
448        mText = "";
449
450        final Resources res = getResources();
451        final CompatibilityInfo compat = res.getCompatibilityInfo();
452
453        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
454        mTextPaint.density = res.getDisplayMetrics().density;
455        mTextPaint.setCompatibilityScaling(compat.applicationScale);
456
457        // If we get the paint from the skin, we should set it to left, since
458        // the layout always wants it to be left.
459        // mTextPaint.setTextAlign(Paint.Align.LEFT);
460
461        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
462        mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
463
464        mMovement = getDefaultMovementMethod();
465        mTransformation = null;
466
467        int textColorHighlight = 0;
468        ColorStateList textColor = null;
469        ColorStateList textColorHint = null;
470        ColorStateList textColorLink = null;
471        int textSize = 15;
472        int typefaceIndex = -1;
473        int styleIndex = -1;
474        boolean allCaps = false;
475
476        final Resources.Theme theme = context.getTheme();
477
478        /*
479         * Look the appearance up without checking first if it exists because
480         * almost every TextView has one and it greatly simplifies the logic
481         * to be able to parse the appearance first and then let specific tags
482         * for this View override it.
483         */
484        TypedArray a = theme.obtainStyledAttributes(
485                    attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
486        TypedArray appearance = null;
487        int ap = a.getResourceId(
488                com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
489        a.recycle();
490        if (ap != -1) {
491            appearance = theme.obtainStyledAttributes(
492                    ap, com.android.internal.R.styleable.TextAppearance);
493        }
494        if (appearance != null) {
495            int n = appearance.getIndexCount();
496            for (int i = 0; i < n; i++) {
497                int attr = appearance.getIndex(i);
498
499                switch (attr) {
500                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
501                    textColorHighlight = appearance.getColor(attr, textColorHighlight);
502                    break;
503
504                case com.android.internal.R.styleable.TextAppearance_textColor:
505                    textColor = appearance.getColorStateList(attr);
506                    break;
507
508                case com.android.internal.R.styleable.TextAppearance_textColorHint:
509                    textColorHint = appearance.getColorStateList(attr);
510                    break;
511
512                case com.android.internal.R.styleable.TextAppearance_textColorLink:
513                    textColorLink = appearance.getColorStateList(attr);
514                    break;
515
516                case com.android.internal.R.styleable.TextAppearance_textSize:
517                    textSize = appearance.getDimensionPixelSize(attr, textSize);
518                    break;
519
520                case com.android.internal.R.styleable.TextAppearance_typeface:
521                    typefaceIndex = appearance.getInt(attr, -1);
522                    break;
523
524                case com.android.internal.R.styleable.TextAppearance_textStyle:
525                    styleIndex = appearance.getInt(attr, -1);
526                    break;
527
528                case com.android.internal.R.styleable.TextAppearance_textAllCaps:
529                    allCaps = appearance.getBoolean(attr, false);
530                    break;
531                }
532            }
533
534            appearance.recycle();
535        }
536
537        boolean editable = getDefaultEditable();
538        CharSequence inputMethod = null;
539        int numeric = 0;
540        CharSequence digits = null;
541        boolean phone = false;
542        boolean autotext = false;
543        int autocap = -1;
544        int buffertype = 0;
545        boolean selectallonfocus = false;
546        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
547            drawableBottom = null, drawableStart = null, drawableEnd = null;
548        int drawablePadding = 0;
549        int ellipsize = -1;
550        boolean singleLine = false;
551        int maxlength = -1;
552        CharSequence text = "";
553        CharSequence hint = null;
554        int shadowcolor = 0;
555        float dx = 0, dy = 0, r = 0;
556        boolean password = false;
557        int inputType = EditorInfo.TYPE_NULL;
558
559        a = theme.obtainStyledAttributes(
560                    attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
561
562        int n = a.getIndexCount();
563        for (int i = 0; i < n; i++) {
564            int attr = a.getIndex(i);
565
566            switch (attr) {
567            case com.android.internal.R.styleable.TextView_editable:
568                editable = a.getBoolean(attr, editable);
569                break;
570
571            case com.android.internal.R.styleable.TextView_inputMethod:
572                inputMethod = a.getText(attr);
573                break;
574
575            case com.android.internal.R.styleable.TextView_numeric:
576                numeric = a.getInt(attr, numeric);
577                break;
578
579            case com.android.internal.R.styleable.TextView_digits:
580                digits = a.getText(attr);
581                break;
582
583            case com.android.internal.R.styleable.TextView_phoneNumber:
584                phone = a.getBoolean(attr, phone);
585                break;
586
587            case com.android.internal.R.styleable.TextView_autoText:
588                autotext = a.getBoolean(attr, autotext);
589                break;
590
591            case com.android.internal.R.styleable.TextView_capitalize:
592                autocap = a.getInt(attr, autocap);
593                break;
594
595            case com.android.internal.R.styleable.TextView_bufferType:
596                buffertype = a.getInt(attr, buffertype);
597                break;
598
599            case com.android.internal.R.styleable.TextView_selectAllOnFocus:
600                selectallonfocus = a.getBoolean(attr, selectallonfocus);
601                break;
602
603            case com.android.internal.R.styleable.TextView_autoLink:
604                mAutoLinkMask = a.getInt(attr, 0);
605                break;
606
607            case com.android.internal.R.styleable.TextView_linksClickable:
608                mLinksClickable = a.getBoolean(attr, true);
609                break;
610
611//            TODO uncomment when this attribute is made public in the next release
612//                 also add TextView_showSoftInputOnFocus to the list of attributes above
613//            case com.android.internal.R.styleable.TextView_showSoftInputOnFocus:
614//                setShowSoftInputOnFocus(a.getBoolean(attr, true));
615//                break;
616
617            case com.android.internal.R.styleable.TextView_drawableLeft:
618                drawableLeft = a.getDrawable(attr);
619                break;
620
621            case com.android.internal.R.styleable.TextView_drawableTop:
622                drawableTop = a.getDrawable(attr);
623                break;
624
625            case com.android.internal.R.styleable.TextView_drawableRight:
626                drawableRight = a.getDrawable(attr);
627                break;
628
629            case com.android.internal.R.styleable.TextView_drawableBottom:
630                drawableBottom = a.getDrawable(attr);
631                break;
632
633            case com.android.internal.R.styleable.TextView_drawableStart:
634                drawableStart = a.getDrawable(attr);
635                break;
636
637            case com.android.internal.R.styleable.TextView_drawableEnd:
638                drawableEnd = a.getDrawable(attr);
639                break;
640
641            case com.android.internal.R.styleable.TextView_drawablePadding:
642                drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
643                break;
644
645            case com.android.internal.R.styleable.TextView_maxLines:
646                setMaxLines(a.getInt(attr, -1));
647                break;
648
649            case com.android.internal.R.styleable.TextView_maxHeight:
650                setMaxHeight(a.getDimensionPixelSize(attr, -1));
651                break;
652
653            case com.android.internal.R.styleable.TextView_lines:
654                setLines(a.getInt(attr, -1));
655                break;
656
657            case com.android.internal.R.styleable.TextView_height:
658                setHeight(a.getDimensionPixelSize(attr, -1));
659                break;
660
661            case com.android.internal.R.styleable.TextView_minLines:
662                setMinLines(a.getInt(attr, -1));
663                break;
664
665            case com.android.internal.R.styleable.TextView_minHeight:
666                setMinHeight(a.getDimensionPixelSize(attr, -1));
667                break;
668
669            case com.android.internal.R.styleable.TextView_maxEms:
670                setMaxEms(a.getInt(attr, -1));
671                break;
672
673            case com.android.internal.R.styleable.TextView_maxWidth:
674                setMaxWidth(a.getDimensionPixelSize(attr, -1));
675                break;
676
677            case com.android.internal.R.styleable.TextView_ems:
678                setEms(a.getInt(attr, -1));
679                break;
680
681            case com.android.internal.R.styleable.TextView_width:
682                setWidth(a.getDimensionPixelSize(attr, -1));
683                break;
684
685            case com.android.internal.R.styleable.TextView_minEms:
686                setMinEms(a.getInt(attr, -1));
687                break;
688
689            case com.android.internal.R.styleable.TextView_minWidth:
690                setMinWidth(a.getDimensionPixelSize(attr, -1));
691                break;
692
693            case com.android.internal.R.styleable.TextView_gravity:
694                setGravity(a.getInt(attr, -1));
695                break;
696
697            case com.android.internal.R.styleable.TextView_hint:
698                hint = a.getText(attr);
699                break;
700
701            case com.android.internal.R.styleable.TextView_text:
702                text = a.getText(attr);
703                break;
704
705            case com.android.internal.R.styleable.TextView_scrollHorizontally:
706                if (a.getBoolean(attr, false)) {
707                    setHorizontallyScrolling(true);
708                }
709                break;
710
711            case com.android.internal.R.styleable.TextView_singleLine:
712                singleLine = a.getBoolean(attr, singleLine);
713                break;
714
715            case com.android.internal.R.styleable.TextView_ellipsize:
716                ellipsize = a.getInt(attr, ellipsize);
717                break;
718
719            case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
720                setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
721                break;
722
723            case com.android.internal.R.styleable.TextView_includeFontPadding:
724                if (!a.getBoolean(attr, true)) {
725                    setIncludeFontPadding(false);
726                }
727                break;
728
729            case com.android.internal.R.styleable.TextView_cursorVisible:
730                if (!a.getBoolean(attr, true)) {
731                    setCursorVisible(false);
732                }
733                break;
734
735            case com.android.internal.R.styleable.TextView_maxLength:
736                maxlength = a.getInt(attr, -1);
737                break;
738
739            case com.android.internal.R.styleable.TextView_textScaleX:
740                setTextScaleX(a.getFloat(attr, 1.0f));
741                break;
742
743            case com.android.internal.R.styleable.TextView_freezesText:
744                mFreezesText = a.getBoolean(attr, false);
745                break;
746
747            case com.android.internal.R.styleable.TextView_shadowColor:
748                shadowcolor = a.getInt(attr, 0);
749                break;
750
751            case com.android.internal.R.styleable.TextView_shadowDx:
752                dx = a.getFloat(attr, 0);
753                break;
754
755            case com.android.internal.R.styleable.TextView_shadowDy:
756                dy = a.getFloat(attr, 0);
757                break;
758
759            case com.android.internal.R.styleable.TextView_shadowRadius:
760                r = a.getFloat(attr, 0);
761                break;
762
763            case com.android.internal.R.styleable.TextView_enabled:
764                setEnabled(a.getBoolean(attr, isEnabled()));
765                break;
766
767            case com.android.internal.R.styleable.TextView_textColorHighlight:
768                textColorHighlight = a.getColor(attr, textColorHighlight);
769                break;
770
771            case com.android.internal.R.styleable.TextView_textColor:
772                textColor = a.getColorStateList(attr);
773                break;
774
775            case com.android.internal.R.styleable.TextView_textColorHint:
776                textColorHint = a.getColorStateList(attr);
777                break;
778
779            case com.android.internal.R.styleable.TextView_textColorLink:
780                textColorLink = a.getColorStateList(attr);
781                break;
782
783            case com.android.internal.R.styleable.TextView_textSize:
784                textSize = a.getDimensionPixelSize(attr, textSize);
785                break;
786
787            case com.android.internal.R.styleable.TextView_typeface:
788                typefaceIndex = a.getInt(attr, typefaceIndex);
789                break;
790
791            case com.android.internal.R.styleable.TextView_textStyle:
792                styleIndex = a.getInt(attr, styleIndex);
793                break;
794
795            case com.android.internal.R.styleable.TextView_password:
796                password = a.getBoolean(attr, password);
797                break;
798
799            case com.android.internal.R.styleable.TextView_lineSpacingExtra:
800                mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
801                break;
802
803            case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
804                mSpacingMult = a.getFloat(attr, mSpacingMult);
805                break;
806
807            case com.android.internal.R.styleable.TextView_inputType:
808                inputType = a.getInt(attr, mInputType);
809                break;
810
811            case com.android.internal.R.styleable.TextView_imeOptions:
812                if (mInputContentType == null) {
813                    mInputContentType = new InputContentType();
814                }
815                mInputContentType.imeOptions = a.getInt(attr,
816                        mInputContentType.imeOptions);
817                break;
818
819            case com.android.internal.R.styleable.TextView_imeActionLabel:
820                if (mInputContentType == null) {
821                    mInputContentType = new InputContentType();
822                }
823                mInputContentType.imeActionLabel = a.getText(attr);
824                break;
825
826            case com.android.internal.R.styleable.TextView_imeActionId:
827                if (mInputContentType == null) {
828                    mInputContentType = new InputContentType();
829                }
830                mInputContentType.imeActionId = a.getInt(attr,
831                        mInputContentType.imeActionId);
832                break;
833
834            case com.android.internal.R.styleable.TextView_privateImeOptions:
835                setPrivateImeOptions(a.getString(attr));
836                break;
837
838            case com.android.internal.R.styleable.TextView_editorExtras:
839                try {
840                    setInputExtras(a.getResourceId(attr, 0));
841                } catch (XmlPullParserException e) {
842                    Log.w(LOG_TAG, "Failure reading input extras", e);
843                } catch (IOException e) {
844                    Log.w(LOG_TAG, "Failure reading input extras", e);
845                }
846                break;
847
848            case com.android.internal.R.styleable.TextView_textCursorDrawable:
849                mCursorDrawableRes = a.getResourceId(attr, 0);
850                break;
851
852            case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
853                mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
854                break;
855
856            case com.android.internal.R.styleable.TextView_textSelectHandleRight:
857                mTextSelectHandleRightRes = a.getResourceId(attr, 0);
858                break;
859
860            case com.android.internal.R.styleable.TextView_textSelectHandle:
861                mTextSelectHandleRes = a.getResourceId(attr, 0);
862                break;
863
864            case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
865                mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
866                break;
867
868            case com.android.internal.R.styleable.TextView_textIsSelectable:
869                mTextIsSelectable = a.getBoolean(attr, false);
870                break;
871
872            case com.android.internal.R.styleable.TextView_textAllCaps:
873                allCaps = a.getBoolean(attr, false);
874                break;
875            }
876        }
877        a.recycle();
878
879        BufferType bufferType = BufferType.EDITABLE;
880
881        final int variation =
882                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
883        final boolean passwordInputType = variation
884                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
885        final boolean webPasswordInputType = variation
886                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
887        final boolean numberPasswordInputType = variation
888                == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
889
890        if (inputMethod != null) {
891            Class<?> c;
892
893            try {
894                c = Class.forName(inputMethod.toString());
895            } catch (ClassNotFoundException ex) {
896                throw new RuntimeException(ex);
897            }
898
899            try {
900                mInput = (KeyListener) c.newInstance();
901            } catch (InstantiationException ex) {
902                throw new RuntimeException(ex);
903            } catch (IllegalAccessException ex) {
904                throw new RuntimeException(ex);
905            }
906            try {
907                mInputType = inputType != EditorInfo.TYPE_NULL
908                        ? inputType
909                        : mInput.getInputType();
910            } catch (IncompatibleClassChangeError e) {
911                mInputType = EditorInfo.TYPE_CLASS_TEXT;
912            }
913        } else if (digits != null) {
914            mInput = DigitsKeyListener.getInstance(digits.toString());
915            // If no input type was specified, we will default to generic
916            // text, since we can't tell the IME about the set of digits
917            // that was selected.
918            mInputType = inputType != EditorInfo.TYPE_NULL
919                    ? inputType : EditorInfo.TYPE_CLASS_TEXT;
920        } else if (inputType != EditorInfo.TYPE_NULL) {
921            setInputType(inputType, true);
922            // If set, the input type overrides what was set using the deprecated singleLine flag.
923            singleLine = !isMultilineInputType(inputType);
924        } else if (phone) {
925            mInput = DialerKeyListener.getInstance();
926            mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
927        } else if (numeric != 0) {
928            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
929                                                   (numeric & DECIMAL) != 0);
930            inputType = EditorInfo.TYPE_CLASS_NUMBER;
931            if ((numeric & SIGNED) != 0) {
932                inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
933            }
934            if ((numeric & DECIMAL) != 0) {
935                inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
936            }
937            mInputType = inputType;
938        } else if (autotext || autocap != -1) {
939            TextKeyListener.Capitalize cap;
940
941            inputType = EditorInfo.TYPE_CLASS_TEXT;
942
943            switch (autocap) {
944            case 1:
945                cap = TextKeyListener.Capitalize.SENTENCES;
946                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
947                break;
948
949            case 2:
950                cap = TextKeyListener.Capitalize.WORDS;
951                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
952                break;
953
954            case 3:
955                cap = TextKeyListener.Capitalize.CHARACTERS;
956                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
957                break;
958
959            default:
960                cap = TextKeyListener.Capitalize.NONE;
961                break;
962            }
963
964            mInput = TextKeyListener.getInstance(autotext, cap);
965            mInputType = inputType;
966        } else if (mTextIsSelectable) {
967            // Prevent text changes from keyboard.
968            mInputType = EditorInfo.TYPE_NULL;
969            mInput = null;
970            bufferType = BufferType.SPANNABLE;
971            // Required to request focus while in touch mode.
972            setFocusableInTouchMode(true);
973            // So that selection can be changed using arrow keys and touch is handled.
974            setMovementMethod(ArrowKeyMovementMethod.getInstance());
975        } else if (editable) {
976            mInput = TextKeyListener.getInstance();
977            mInputType = EditorInfo.TYPE_CLASS_TEXT;
978        } else {
979            mInput = null;
980
981            switch (buffertype) {
982                case 0:
983                    bufferType = BufferType.NORMAL;
984                    break;
985                case 1:
986                    bufferType = BufferType.SPANNABLE;
987                    break;
988                case 2:
989                    bufferType = BufferType.EDITABLE;
990                    break;
991            }
992        }
993
994        // mInputType has been set from inputType, possibly modified by mInputMethod.
995        // Specialize mInputType to [web]password if we have a text class and the original input
996        // type was a password.
997        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
998            if (password || passwordInputType) {
999                mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
1000                        | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
1001            }
1002            if (webPasswordInputType) {
1003                mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
1004                        | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
1005            }
1006        } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
1007            if (numberPasswordInputType) {
1008                mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
1009                        | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
1010            }
1011        }
1012
1013        if (selectallonfocus) {
1014            mSelectAllOnFocus = true;
1015
1016            if (bufferType == BufferType.NORMAL)
1017                bufferType = BufferType.SPANNABLE;
1018        }
1019
1020        setCompoundDrawablesWithIntrinsicBounds(
1021            drawableLeft, drawableTop, drawableRight, drawableBottom);
1022        setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1023        setCompoundDrawablePadding(drawablePadding);
1024
1025        // Same as setSingleLine(), but make sure the transformation method and the maximum number
1026        // of lines of height are unchanged for multi-line TextViews.
1027        setInputTypeSingleLine(singleLine);
1028        applySingleLine(singleLine, singleLine, singleLine);
1029
1030        if (singleLine && mInput == null && ellipsize < 0) {
1031                ellipsize = 3; // END
1032        }
1033
1034        switch (ellipsize) {
1035            case 1:
1036                setEllipsize(TextUtils.TruncateAt.START);
1037                break;
1038            case 2:
1039                setEllipsize(TextUtils.TruncateAt.MIDDLE);
1040                break;
1041            case 3:
1042                setEllipsize(TextUtils.TruncateAt.END);
1043                break;
1044            case 4:
1045                if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1046                    setHorizontalFadingEdgeEnabled(true);
1047                    mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1048                } else {
1049                    setHorizontalFadingEdgeEnabled(false);
1050                    mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1051                }
1052                setEllipsize(TextUtils.TruncateAt.MARQUEE);
1053                break;
1054        }
1055
1056        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1057        setHintTextColor(textColorHint);
1058        setLinkTextColor(textColorLink);
1059        if (textColorHighlight != 0) {
1060            setHighlightColor(textColorHighlight);
1061        }
1062        setRawTextSize(textSize);
1063
1064        if (allCaps) {
1065            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1066        }
1067
1068        if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1069            setTransformationMethod(PasswordTransformationMethod.getInstance());
1070            typefaceIndex = MONOSPACE;
1071        } else if ((mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1072                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1073            typefaceIndex = MONOSPACE;
1074        }
1075
1076        setTypefaceByIndex(typefaceIndex, styleIndex);
1077
1078        if (shadowcolor != 0) {
1079            setShadowLayer(r, dx, dy, shadowcolor);
1080        }
1081
1082        if (maxlength >= 0) {
1083            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1084        } else {
1085            setFilters(NO_FILTERS);
1086        }
1087
1088        setText(text, bufferType);
1089        if (hint != null) setHint(hint);
1090
1091        /*
1092         * Views are not normally focusable unless specified to be.
1093         * However, TextViews that have input or movement methods *are*
1094         * focusable by default.
1095         */
1096        a = context.obtainStyledAttributes(attrs,
1097                                           com.android.internal.R.styleable.View,
1098                                           defStyle, 0);
1099
1100        boolean focusable = mMovement != null || mInput != null;
1101        boolean clickable = focusable;
1102        boolean longClickable = focusable;
1103
1104        n = a.getIndexCount();
1105        for (int i = 0; i < n; i++) {
1106            int attr = a.getIndex(i);
1107
1108            switch (attr) {
1109            case com.android.internal.R.styleable.View_focusable:
1110                focusable = a.getBoolean(attr, focusable);
1111                break;
1112
1113            case com.android.internal.R.styleable.View_clickable:
1114                clickable = a.getBoolean(attr, clickable);
1115                break;
1116
1117            case com.android.internal.R.styleable.View_longClickable:
1118                longClickable = a.getBoolean(attr, longClickable);
1119                break;
1120            }
1121        }
1122        a.recycle();
1123
1124        setFocusable(focusable);
1125        setClickable(clickable);
1126        setLongClickable(longClickable);
1127
1128        prepareCursorControllers();
1129    }
1130
1131    private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
1132        Typeface tf = null;
1133        switch (typefaceIndex) {
1134            case SANS:
1135                tf = Typeface.SANS_SERIF;
1136                break;
1137
1138            case SERIF:
1139                tf = Typeface.SERIF;
1140                break;
1141
1142            case MONOSPACE:
1143                tf = Typeface.MONOSPACE;
1144                break;
1145        }
1146
1147        setTypeface(tf, styleIndex);
1148    }
1149
1150    private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1151        boolean hasRelativeDrawables = (start != null) || (end != null);
1152        if (hasRelativeDrawables) {
1153            Drawables dr = mDrawables;
1154            if (dr == null) {
1155                mDrawables = dr = new Drawables();
1156            }
1157            final Rect compoundRect = dr.mCompoundRect;
1158            int[] state = getDrawableState();
1159            if (start != null) {
1160                start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1161                start.setState(state);
1162                start.copyBounds(compoundRect);
1163                start.setCallback(this);
1164
1165                dr.mDrawableStart = start;
1166                dr.mDrawableSizeStart = compoundRect.width();
1167                dr.mDrawableHeightStart = compoundRect.height();
1168            } else {
1169                dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1170            }
1171            if (end != null) {
1172                end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1173                end.setState(state);
1174                end.copyBounds(compoundRect);
1175                end.setCallback(this);
1176
1177                dr.mDrawableEnd = end;
1178                dr.mDrawableSizeEnd = compoundRect.width();
1179                dr.mDrawableHeightEnd = compoundRect.height();
1180            } else {
1181                dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1182            }
1183        }
1184    }
1185
1186    @Override
1187    public void setEnabled(boolean enabled) {
1188        if (enabled == isEnabled()) {
1189            return;
1190        }
1191
1192        if (!enabled) {
1193            // Hide the soft input if the currently active TextView is disabled
1194            InputMethodManager imm = InputMethodManager.peekInstance();
1195            if (imm != null && imm.isActive(this)) {
1196                imm.hideSoftInputFromWindow(getWindowToken(), 0);
1197            }
1198        }
1199
1200        super.setEnabled(enabled);
1201
1202        if (enabled) {
1203            // Make sure IME is updated with current editor info.
1204            InputMethodManager imm = InputMethodManager.peekInstance();
1205            if (imm != null) imm.restartInput(this);
1206        }
1207
1208        prepareCursorControllers();
1209
1210        // start or stop the cursor blinking as appropriate
1211        makeBlink();
1212    }
1213
1214    /**
1215     * Sets the typeface and style in which the text should be displayed,
1216     * and turns on the fake bold and italic bits in the Paint if the
1217     * Typeface that you provided does not have all the bits in the
1218     * style that you specified.
1219     *
1220     * @attr ref android.R.styleable#TextView_typeface
1221     * @attr ref android.R.styleable#TextView_textStyle
1222     */
1223    public void setTypeface(Typeface tf, int style) {
1224        if (style > 0) {
1225            if (tf == null) {
1226                tf = Typeface.defaultFromStyle(style);
1227            } else {
1228                tf = Typeface.create(tf, style);
1229            }
1230
1231            setTypeface(tf);
1232            // now compute what (if any) algorithmic styling is needed
1233            int typefaceStyle = tf != null ? tf.getStyle() : 0;
1234            int need = style & ~typefaceStyle;
1235            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1236            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1237        } else {
1238            mTextPaint.setFakeBoldText(false);
1239            mTextPaint.setTextSkewX(0);
1240            setTypeface(tf);
1241        }
1242    }
1243
1244    /**
1245     * Subclasses override this to specify that they have a KeyListener
1246     * by default even if not specifically called for in the XML options.
1247     */
1248    protected boolean getDefaultEditable() {
1249        return false;
1250    }
1251
1252    /**
1253     * Subclasses override this to specify a default movement method.
1254     */
1255    protected MovementMethod getDefaultMovementMethod() {
1256        return null;
1257    }
1258
1259    /**
1260     * Return the text the TextView is displaying. If setText() was called with
1261     * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1262     * the return value from this method to Spannable or Editable, respectively.
1263     *
1264     * Note: The content of the return value should not be modified. If you want
1265     * a modifiable one, you should make your own copy first.
1266     */
1267    @ViewDebug.CapturedViewProperty
1268    public CharSequence getText() {
1269        return mText;
1270    }
1271
1272    /**
1273     * Returns the length, in characters, of the text managed by this TextView
1274     */
1275    public int length() {
1276        return mText.length();
1277    }
1278
1279    /**
1280     * Return the text the TextView is displaying as an Editable object.  If
1281     * the text is not editable, null is returned.
1282     *
1283     * @see #getText
1284     */
1285    public Editable getEditableText() {
1286        return (mText instanceof Editable) ? (Editable)mText : null;
1287    }
1288
1289    /**
1290     * @return the height of one standard line in pixels.  Note that markup
1291     * within the text can cause individual lines to be taller or shorter
1292     * than this height, and the layout may contain additional first-
1293     * or last-line padding.
1294     */
1295    public int getLineHeight() {
1296        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1297    }
1298
1299    /**
1300     * @return the Layout that is currently being used to display the text.
1301     * This can be null if the text or width has recently changes.
1302     */
1303    public final Layout getLayout() {
1304        return mLayout;
1305    }
1306
1307    /**
1308     * @return the current key listener for this TextView.
1309     * This will frequently be null for non-EditText TextViews.
1310     */
1311    public final KeyListener getKeyListener() {
1312        return mInput;
1313    }
1314
1315    /**
1316     * Sets the key listener to be used with this TextView.  This can be null
1317     * to disallow user input.  Note that this method has significant and
1318     * subtle interactions with soft keyboards and other input method:
1319     * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1320     * for important details.  Calling this method will replace the current
1321     * content type of the text view with the content type returned by the
1322     * key listener.
1323     * <p>
1324     * Be warned that if you want a TextView with a key listener or movement
1325     * method not to be focusable, or if you want a TextView without a
1326     * key listener or movement method to be focusable, you must call
1327     * {@link #setFocusable} again after calling this to get the focusability
1328     * back the way you want it.
1329     *
1330     * @attr ref android.R.styleable#TextView_numeric
1331     * @attr ref android.R.styleable#TextView_digits
1332     * @attr ref android.R.styleable#TextView_phoneNumber
1333     * @attr ref android.R.styleable#TextView_inputMethod
1334     * @attr ref android.R.styleable#TextView_capitalize
1335     * @attr ref android.R.styleable#TextView_autoText
1336     */
1337    public void setKeyListener(KeyListener input) {
1338        setKeyListenerOnly(input);
1339        fixFocusableAndClickableSettings();
1340
1341        if (input != null) {
1342            try {
1343                mInputType = mInput.getInputType();
1344            } catch (IncompatibleClassChangeError e) {
1345                mInputType = EditorInfo.TYPE_CLASS_TEXT;
1346            }
1347            // Change inputType, without affecting transformation.
1348            // No need to applySingleLine since mSingleLine is unchanged.
1349            setInputTypeSingleLine(mSingleLine);
1350        } else {
1351            mInputType = EditorInfo.TYPE_NULL;
1352        }
1353
1354        InputMethodManager imm = InputMethodManager.peekInstance();
1355        if (imm != null) imm.restartInput(this);
1356    }
1357
1358    private void setKeyListenerOnly(KeyListener input) {
1359        mInput = input;
1360        if (mInput != null && !(mText instanceof Editable))
1361            setText(mText);
1362
1363        setFilters((Editable) mText, mFilters);
1364    }
1365
1366    /**
1367     * @return the movement method being used for this TextView.
1368     * This will frequently be null for non-EditText TextViews.
1369     */
1370    public final MovementMethod getMovementMethod() {
1371        return mMovement;
1372    }
1373
1374    /**
1375     * Sets the movement method (arrow key handler) to be used for
1376     * this TextView.  This can be null to disallow using the arrow keys
1377     * to move the cursor or scroll the view.
1378     * <p>
1379     * Be warned that if you want a TextView with a key listener or movement
1380     * method not to be focusable, or if you want a TextView without a
1381     * key listener or movement method to be focusable, you must call
1382     * {@link #setFocusable} again after calling this to get the focusability
1383     * back the way you want it.
1384     */
1385    public final void setMovementMethod(MovementMethod movement) {
1386        mMovement = movement;
1387
1388        if (mMovement != null && !(mText instanceof Spannable))
1389            setText(mText);
1390
1391        fixFocusableAndClickableSettings();
1392
1393        // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
1394        prepareCursorControllers();
1395    }
1396
1397    private void fixFocusableAndClickableSettings() {
1398        if ((mMovement != null) || mInput != null) {
1399            setFocusable(true);
1400            setClickable(true);
1401            setLongClickable(true);
1402        } else {
1403            setFocusable(false);
1404            setClickable(false);
1405            setLongClickable(false);
1406        }
1407    }
1408
1409    /**
1410     * @return the current transformation method for this TextView.
1411     * This will frequently be null except for single-line and password
1412     * fields.
1413     */
1414    public final TransformationMethod getTransformationMethod() {
1415        return mTransformation;
1416    }
1417
1418    /**
1419     * Sets the transformation that is applied to the text that this
1420     * TextView is displaying.
1421     *
1422     * @attr ref android.R.styleable#TextView_password
1423     * @attr ref android.R.styleable#TextView_singleLine
1424     */
1425    public final void setTransformationMethod(TransformationMethod method) {
1426        if (method == mTransformation) {
1427            // Avoid the setText() below if the transformation is
1428            // the same.
1429            return;
1430        }
1431        if (mTransformation != null) {
1432            if (mText instanceof Spannable) {
1433                ((Spannable) mText).removeSpan(mTransformation);
1434            }
1435        }
1436
1437        mTransformation = method;
1438
1439        if (method instanceof TransformationMethod2) {
1440            TransformationMethod2 method2 = (TransformationMethod2) method;
1441            mAllowTransformationLengthChange = !mTextIsSelectable && !(mText instanceof Editable);
1442            method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1443        } else {
1444            mAllowTransformationLengthChange = false;
1445        }
1446
1447        setText(mText);
1448    }
1449
1450    /**
1451     * Returns the top padding of the view, plus space for the top
1452     * Drawable if any.
1453     */
1454    public int getCompoundPaddingTop() {
1455        final Drawables dr = mDrawables;
1456        if (dr == null || dr.mDrawableTop == null) {
1457            return mPaddingTop;
1458        } else {
1459            return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1460        }
1461    }
1462
1463    /**
1464     * Returns the bottom padding of the view, plus space for the bottom
1465     * Drawable if any.
1466     */
1467    public int getCompoundPaddingBottom() {
1468        final Drawables dr = mDrawables;
1469        if (dr == null || dr.mDrawableBottom == null) {
1470            return mPaddingBottom;
1471        } else {
1472            return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1473        }
1474    }
1475
1476    /**
1477     * Returns the left padding of the view, plus space for the left
1478     * Drawable if any.
1479     */
1480    public int getCompoundPaddingLeft() {
1481        final Drawables dr = mDrawables;
1482        if (dr == null || dr.mDrawableLeft == null) {
1483            return mPaddingLeft;
1484        } else {
1485            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1486        }
1487    }
1488
1489    /**
1490     * Returns the right padding of the view, plus space for the right
1491     * Drawable if any.
1492     */
1493    public int getCompoundPaddingRight() {
1494        final Drawables dr = mDrawables;
1495        if (dr == null || dr.mDrawableRight == null) {
1496            return mPaddingRight;
1497        } else {
1498            return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1499        }
1500    }
1501
1502    /**
1503     * Returns the start padding of the view, plus space for the start
1504     * Drawable if any.
1505     *
1506     * @hide
1507     */
1508    public int getCompoundPaddingStart() {
1509        resolveDrawables();
1510        switch(getResolvedLayoutDirection()) {
1511            default:
1512            case LAYOUT_DIRECTION_LTR:
1513                return getCompoundPaddingLeft();
1514            case LAYOUT_DIRECTION_RTL:
1515                return getCompoundPaddingRight();
1516        }
1517    }
1518
1519    /**
1520     * Returns the end padding of the view, plus space for the end
1521     * Drawable if any.
1522     *
1523     * @hide
1524     */
1525    public int getCompoundPaddingEnd() {
1526        resolveDrawables();
1527        switch(getResolvedLayoutDirection()) {
1528            default:
1529            case LAYOUT_DIRECTION_LTR:
1530                return getCompoundPaddingRight();
1531            case LAYOUT_DIRECTION_RTL:
1532                return getCompoundPaddingLeft();
1533        }
1534    }
1535
1536    /**
1537     * Returns the extended top padding of the view, including both the
1538     * top Drawable if any and any extra space to keep more than maxLines
1539     * of text from showing.  It is only valid to call this after measuring.
1540     */
1541    public int getExtendedPaddingTop() {
1542        if (mMaxMode != LINES) {
1543            return getCompoundPaddingTop();
1544        }
1545
1546        if (mLayout.getLineCount() <= mMaximum) {
1547            return getCompoundPaddingTop();
1548        }
1549
1550        int top = getCompoundPaddingTop();
1551        int bottom = getCompoundPaddingBottom();
1552        int viewht = getHeight() - top - bottom;
1553        int layoutht = mLayout.getLineTop(mMaximum);
1554
1555        if (layoutht >= viewht) {
1556            return top;
1557        }
1558
1559        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1560        if (gravity == Gravity.TOP) {
1561            return top;
1562        } else if (gravity == Gravity.BOTTOM) {
1563            return top + viewht - layoutht;
1564        } else { // (gravity == Gravity.CENTER_VERTICAL)
1565            return top + (viewht - layoutht) / 2;
1566        }
1567    }
1568
1569    /**
1570     * Returns the extended bottom padding of the view, including both the
1571     * bottom Drawable if any and any extra space to keep more than maxLines
1572     * of text from showing.  It is only valid to call this after measuring.
1573     */
1574    public int getExtendedPaddingBottom() {
1575        if (mMaxMode != LINES) {
1576            return getCompoundPaddingBottom();
1577        }
1578
1579        if (mLayout.getLineCount() <= mMaximum) {
1580            return getCompoundPaddingBottom();
1581        }
1582
1583        int top = getCompoundPaddingTop();
1584        int bottom = getCompoundPaddingBottom();
1585        int viewht = getHeight() - top - bottom;
1586        int layoutht = mLayout.getLineTop(mMaximum);
1587
1588        if (layoutht >= viewht) {
1589            return bottom;
1590        }
1591
1592        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1593        if (gravity == Gravity.TOP) {
1594            return bottom + viewht - layoutht;
1595        } else if (gravity == Gravity.BOTTOM) {
1596            return bottom;
1597        } else { // (gravity == Gravity.CENTER_VERTICAL)
1598            return bottom + (viewht - layoutht) / 2;
1599        }
1600    }
1601
1602    /**
1603     * Returns the total left padding of the view, including the left
1604     * Drawable if any.
1605     */
1606    public int getTotalPaddingLeft() {
1607        return getCompoundPaddingLeft();
1608    }
1609
1610    /**
1611     * Returns the total right padding of the view, including the right
1612     * Drawable if any.
1613     */
1614    public int getTotalPaddingRight() {
1615        return getCompoundPaddingRight();
1616    }
1617
1618    /**
1619     * Returns the total start padding of the view, including the start
1620     * Drawable if any.
1621     *
1622     * @hide
1623     */
1624    public int getTotalPaddingStart() {
1625        return getCompoundPaddingStart();
1626    }
1627
1628    /**
1629     * Returns the total end padding of the view, including the end
1630     * Drawable if any.
1631     *
1632     * @hide
1633     */
1634    public int getTotalPaddingEnd() {
1635        return getCompoundPaddingEnd();
1636    }
1637
1638    /**
1639     * Returns the total top padding of the view, including the top
1640     * Drawable if any, the extra space to keep more than maxLines
1641     * from showing, and the vertical offset for gravity, if any.
1642     */
1643    public int getTotalPaddingTop() {
1644        return getExtendedPaddingTop() + getVerticalOffset(true);
1645    }
1646
1647    /**
1648     * Returns the total bottom padding of the view, including the bottom
1649     * Drawable if any, the extra space to keep more than maxLines
1650     * from showing, and the vertical offset for gravity, if any.
1651     */
1652    public int getTotalPaddingBottom() {
1653        return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1654    }
1655
1656    /**
1657     * Sets the Drawables (if any) to appear to the left of, above,
1658     * to the right of, and below the text.  Use null if you do not
1659     * want a Drawable there.  The Drawables must already have had
1660     * {@link Drawable#setBounds} called.
1661     *
1662     * @attr ref android.R.styleable#TextView_drawableLeft
1663     * @attr ref android.R.styleable#TextView_drawableTop
1664     * @attr ref android.R.styleable#TextView_drawableRight
1665     * @attr ref android.R.styleable#TextView_drawableBottom
1666     */
1667    public void setCompoundDrawables(Drawable left, Drawable top,
1668                                     Drawable right, Drawable bottom) {
1669        Drawables dr = mDrawables;
1670
1671        final boolean drawables = left != null || top != null
1672                || right != null || bottom != null;
1673
1674        if (!drawables) {
1675            // Clearing drawables...  can we free the data structure?
1676            if (dr != null) {
1677                if (dr.mDrawablePadding == 0) {
1678                    mDrawables = null;
1679                } else {
1680                    // We need to retain the last set padding, so just clear
1681                    // out all of the fields in the existing structure.
1682                    if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
1683                    dr.mDrawableLeft = null;
1684                    if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
1685                    dr.mDrawableTop = null;
1686                    if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
1687                    dr.mDrawableRight = null;
1688                    if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
1689                    dr.mDrawableBottom = null;
1690                    dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1691                    dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1692                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1693                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1694                }
1695            }
1696        } else {
1697            if (dr == null) {
1698                mDrawables = dr = new Drawables();
1699            }
1700
1701            if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1702                dr.mDrawableLeft.setCallback(null);
1703            }
1704            dr.mDrawableLeft = left;
1705
1706            if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
1707                dr.mDrawableTop.setCallback(null);
1708            }
1709            dr.mDrawableTop = top;
1710
1711            if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
1712                dr.mDrawableRight.setCallback(null);
1713            }
1714            dr.mDrawableRight = right;
1715
1716            if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
1717                dr.mDrawableBottom.setCallback(null);
1718            }
1719            dr.mDrawableBottom = bottom;
1720
1721            final Rect compoundRect = dr.mCompoundRect;
1722            int[] state;
1723
1724            state = getDrawableState();
1725
1726            if (left != null) {
1727                left.setState(state);
1728                left.copyBounds(compoundRect);
1729                left.setCallback(this);
1730                dr.mDrawableSizeLeft = compoundRect.width();
1731                dr.mDrawableHeightLeft = compoundRect.height();
1732            } else {
1733                dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1734            }
1735
1736            if (right != null) {
1737                right.setState(state);
1738                right.copyBounds(compoundRect);
1739                right.setCallback(this);
1740                dr.mDrawableSizeRight = compoundRect.width();
1741                dr.mDrawableHeightRight = compoundRect.height();
1742            } else {
1743                dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1744            }
1745
1746            if (top != null) {
1747                top.setState(state);
1748                top.copyBounds(compoundRect);
1749                top.setCallback(this);
1750                dr.mDrawableSizeTop = compoundRect.height();
1751                dr.mDrawableWidthTop = compoundRect.width();
1752            } else {
1753                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1754            }
1755
1756            if (bottom != null) {
1757                bottom.setState(state);
1758                bottom.copyBounds(compoundRect);
1759                bottom.setCallback(this);
1760                dr.mDrawableSizeBottom = compoundRect.height();
1761                dr.mDrawableWidthBottom = compoundRect.width();
1762            } else {
1763                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1764            }
1765        }
1766
1767        invalidate();
1768        requestLayout();
1769    }
1770
1771    /**
1772     * Sets the Drawables (if any) to appear to the left of, above,
1773     * to the right of, and below the text.  Use 0 if you do not
1774     * want a Drawable there. The Drawables' bounds will be set to
1775     * their intrinsic bounds.
1776     *
1777     * @param left Resource identifier of the left Drawable.
1778     * @param top Resource identifier of the top Drawable.
1779     * @param right Resource identifier of the right Drawable.
1780     * @param bottom Resource identifier of the bottom Drawable.
1781     *
1782     * @attr ref android.R.styleable#TextView_drawableLeft
1783     * @attr ref android.R.styleable#TextView_drawableTop
1784     * @attr ref android.R.styleable#TextView_drawableRight
1785     * @attr ref android.R.styleable#TextView_drawableBottom
1786     */
1787    public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1788        final Resources resources = getContext().getResources();
1789        setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
1790                top != 0 ? resources.getDrawable(top) : null,
1791                right != 0 ? resources.getDrawable(right) : null,
1792                bottom != 0 ? resources.getDrawable(bottom) : null);
1793    }
1794
1795    /**
1796     * Sets the Drawables (if any) to appear to the left of, above,
1797     * to the right of, and below the text.  Use null if you do not
1798     * want a Drawable there. The Drawables' bounds will be set to
1799     * their intrinsic bounds.
1800     *
1801     * @attr ref android.R.styleable#TextView_drawableLeft
1802     * @attr ref android.R.styleable#TextView_drawableTop
1803     * @attr ref android.R.styleable#TextView_drawableRight
1804     * @attr ref android.R.styleable#TextView_drawableBottom
1805     */
1806    public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1807            Drawable right, Drawable bottom) {
1808
1809        if (left != null) {
1810            left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1811        }
1812        if (right != null) {
1813            right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1814        }
1815        if (top != null) {
1816            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1817        }
1818        if (bottom != null) {
1819            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1820        }
1821        setCompoundDrawables(left, top, right, bottom);
1822    }
1823
1824    /**
1825     * Sets the Drawables (if any) to appear to the start of, above,
1826     * to the end of, and below the text.  Use null if you do not
1827     * want a Drawable there.  The Drawables must already have had
1828     * {@link Drawable#setBounds} called.
1829     *
1830     * @attr ref android.R.styleable#TextView_drawableStart
1831     * @attr ref android.R.styleable#TextView_drawableTop
1832     * @attr ref android.R.styleable#TextView_drawableEnd
1833     * @attr ref android.R.styleable#TextView_drawableBottom
1834     *
1835     * @hide
1836     */
1837    public void setCompoundDrawablesRelative(Drawable start, Drawable top,
1838                                     Drawable end, Drawable bottom) {
1839        Drawables dr = mDrawables;
1840
1841        final boolean drawables = start != null || top != null
1842                || end != null || bottom != null;
1843
1844        if (!drawables) {
1845            // Clearing drawables...  can we free the data structure?
1846            if (dr != null) {
1847                if (dr.mDrawablePadding == 0) {
1848                    mDrawables = null;
1849                } else {
1850                    // We need to retain the last set padding, so just clear
1851                    // out all of the fields in the existing structure.
1852                    if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
1853                    dr.mDrawableStart = null;
1854                    if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
1855                    dr.mDrawableTop = null;
1856                    if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
1857                    dr.mDrawableEnd = null;
1858                    if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
1859                    dr.mDrawableBottom = null;
1860                    dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1861                    dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1862                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1863                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1864                }
1865            }
1866        } else {
1867            if (dr == null) {
1868                mDrawables = dr = new Drawables();
1869            }
1870
1871            if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
1872                dr.mDrawableStart.setCallback(null);
1873            }
1874            dr.mDrawableStart = start;
1875
1876            if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
1877                dr.mDrawableTop.setCallback(null);
1878            }
1879            dr.mDrawableTop = top;
1880
1881            if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
1882                dr.mDrawableEnd.setCallback(null);
1883            }
1884            dr.mDrawableEnd = end;
1885
1886            if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
1887                dr.mDrawableBottom.setCallback(null);
1888            }
1889            dr.mDrawableBottom = bottom;
1890
1891            final Rect compoundRect = dr.mCompoundRect;
1892            int[] state;
1893
1894            state = getDrawableState();
1895
1896            if (start != null) {
1897                start.setState(state);
1898                start.copyBounds(compoundRect);
1899                start.setCallback(this);
1900                dr.mDrawableSizeStart = compoundRect.width();
1901                dr.mDrawableHeightStart = compoundRect.height();
1902            } else {
1903                dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1904            }
1905
1906            if (end != null) {
1907                end.setState(state);
1908                end.copyBounds(compoundRect);
1909                end.setCallback(this);
1910                dr.mDrawableSizeEnd = compoundRect.width();
1911                dr.mDrawableHeightEnd = compoundRect.height();
1912            } else {
1913                dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1914            }
1915
1916            if (top != null) {
1917                top.setState(state);
1918                top.copyBounds(compoundRect);
1919                top.setCallback(this);
1920                dr.mDrawableSizeTop = compoundRect.height();
1921                dr.mDrawableWidthTop = compoundRect.width();
1922            } else {
1923                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1924            }
1925
1926            if (bottom != null) {
1927                bottom.setState(state);
1928                bottom.copyBounds(compoundRect);
1929                bottom.setCallback(this);
1930                dr.mDrawableSizeBottom = compoundRect.height();
1931                dr.mDrawableWidthBottom = compoundRect.width();
1932            } else {
1933                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1934            }
1935        }
1936
1937        resolveDrawables();
1938        invalidate();
1939        requestLayout();
1940    }
1941
1942    /**
1943     * Sets the Drawables (if any) to appear to the start of, above,
1944     * to the end of, and below the text.  Use 0 if you do not
1945     * want a Drawable there. The Drawables' bounds will be set to
1946     * their intrinsic bounds.
1947     *
1948     * @param start Resource identifier of the start Drawable.
1949     * @param top Resource identifier of the top Drawable.
1950     * @param end Resource identifier of the end Drawable.
1951     * @param bottom Resource identifier of the bottom Drawable.
1952     *
1953     * @attr ref android.R.styleable#TextView_drawableStart
1954     * @attr ref android.R.styleable#TextView_drawableTop
1955     * @attr ref android.R.styleable#TextView_drawableEnd
1956     * @attr ref android.R.styleable#TextView_drawableBottom
1957     *
1958     * @hide
1959     */
1960    public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
1961            int bottom) {
1962        resetResolvedDrawables();
1963        final Resources resources = getContext().getResources();
1964        setCompoundDrawablesRelativeWithIntrinsicBounds(
1965                start != 0 ? resources.getDrawable(start) : null,
1966                top != 0 ? resources.getDrawable(top) : null,
1967                end != 0 ? resources.getDrawable(end) : null,
1968                bottom != 0 ? resources.getDrawable(bottom) : null);
1969    }
1970
1971    /**
1972     * Sets the Drawables (if any) to appear to the start of, above,
1973     * to the end of, and below the text.  Use null if you do not
1974     * want a Drawable there. The Drawables' bounds will be set to
1975     * their intrinsic bounds.
1976     *
1977     * @attr ref android.R.styleable#TextView_drawableStart
1978     * @attr ref android.R.styleable#TextView_drawableTop
1979     * @attr ref android.R.styleable#TextView_drawableEnd
1980     * @attr ref android.R.styleable#TextView_drawableBottom
1981     *
1982     * @hide
1983     */
1984    public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
1985            Drawable end, Drawable bottom) {
1986
1987        resetResolvedDrawables();
1988        if (start != null) {
1989            start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1990        }
1991        if (end != null) {
1992            end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1993        }
1994        if (top != null) {
1995            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1996        }
1997        if (bottom != null) {
1998            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1999        }
2000        setCompoundDrawablesRelative(start, top, end, bottom);
2001    }
2002
2003    /**
2004     * Returns drawables for the left, top, right, and bottom borders.
2005     */
2006    public Drawable[] getCompoundDrawables() {
2007        final Drawables dr = mDrawables;
2008        if (dr != null) {
2009            return new Drawable[] {
2010                dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2011            };
2012        } else {
2013            return new Drawable[] { null, null, null, null };
2014        }
2015    }
2016
2017    /**
2018     * Returns drawables for the start, top, end, and bottom borders.
2019     *
2020     * @hide
2021     */
2022    public Drawable[] getCompoundDrawablesRelative() {
2023        final Drawables dr = mDrawables;
2024        if (dr != null) {
2025            return new Drawable[] {
2026                dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2027            };
2028        } else {
2029            return new Drawable[] { null, null, null, null };
2030        }
2031    }
2032
2033    /**
2034     * Sets the size of the padding between the compound drawables and
2035     * the text.
2036     *
2037     * @attr ref android.R.styleable#TextView_drawablePadding
2038     */
2039    public void setCompoundDrawablePadding(int pad) {
2040        Drawables dr = mDrawables;
2041        if (pad == 0) {
2042            if (dr != null) {
2043                dr.mDrawablePadding = pad;
2044            }
2045        } else {
2046            if (dr == null) {
2047                mDrawables = dr = new Drawables();
2048            }
2049            dr.mDrawablePadding = pad;
2050        }
2051
2052        invalidate();
2053        requestLayout();
2054    }
2055
2056    /**
2057     * Returns the padding between the compound drawables and the text.
2058     */
2059    public int getCompoundDrawablePadding() {
2060        final Drawables dr = mDrawables;
2061        return dr != null ? dr.mDrawablePadding : 0;
2062    }
2063
2064    @Override
2065    public void setPadding(int left, int top, int right, int bottom) {
2066        if (left != mPaddingLeft ||
2067            right != mPaddingRight ||
2068            top != mPaddingTop ||
2069            bottom != mPaddingBottom) {
2070            nullLayouts();
2071        }
2072
2073        // the super call will requestLayout()
2074        super.setPadding(left, top, right, bottom);
2075        invalidate();
2076    }
2077
2078    /**
2079     * Gets the autolink mask of the text.  See {@link
2080     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2081     * possible values.
2082     *
2083     * @attr ref android.R.styleable#TextView_autoLink
2084     */
2085    public final int getAutoLinkMask() {
2086        return mAutoLinkMask;
2087    }
2088
2089    /**
2090     * Sets the text color, size, style, hint color, and highlight color
2091     * from the specified TextAppearance resource.
2092     */
2093    public void setTextAppearance(Context context, int resid) {
2094        TypedArray appearance =
2095            context.obtainStyledAttributes(resid,
2096                                           com.android.internal.R.styleable.TextAppearance);
2097
2098        int color;
2099        ColorStateList colors;
2100        int ts;
2101
2102        color = appearance.getColor(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    public float getTextSize() {
2152        return mTextPaint.getTextSize();
2153    }
2154
2155    /**
2156     * Set the default text size to the given value, interpreted as "scaled
2157     * pixel" units.  This size is adjusted based on the current density and
2158     * user font size preference.
2159     *
2160     * @param size The scaled pixel size.
2161     *
2162     * @attr ref android.R.styleable#TextView_textSize
2163     */
2164    @android.view.RemotableViewMethod
2165    public void setTextSize(float size) {
2166        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2167    }
2168
2169    /**
2170     * Set the default text size to a given unit and value.  See {@link
2171     * TypedValue} for the possible dimension units.
2172     *
2173     * @param unit The desired dimension unit.
2174     * @param size The desired size in the given units.
2175     *
2176     * @attr ref android.R.styleable#TextView_textSize
2177     */
2178    public void setTextSize(int unit, float size) {
2179        Context c = getContext();
2180        Resources r;
2181
2182        if (c == null)
2183            r = Resources.getSystem();
2184        else
2185            r = c.getResources();
2186
2187        setRawTextSize(TypedValue.applyDimension(
2188            unit, size, r.getDisplayMetrics()));
2189    }
2190
2191    private void setRawTextSize(float size) {
2192        if (size != mTextPaint.getTextSize()) {
2193            mTextPaint.setTextSize(size);
2194
2195            if (mLayout != null) {
2196                nullLayouts();
2197                requestLayout();
2198                invalidate();
2199            }
2200        }
2201    }
2202
2203    /**
2204     * @return the extent by which text is currently being stretched
2205     * horizontally.  This will usually be 1.
2206     */
2207    public float getTextScaleX() {
2208        return mTextPaint.getTextScaleX();
2209    }
2210
2211    /**
2212     * Sets the extent by which text should be stretched horizontally.
2213     *
2214     * @attr ref android.R.styleable#TextView_textScaleX
2215     */
2216    @android.view.RemotableViewMethod
2217    public void setTextScaleX(float size) {
2218        if (size != mTextPaint.getTextScaleX()) {
2219            mUserSetTextScaleX = true;
2220            mTextPaint.setTextScaleX(size);
2221
2222            if (mLayout != null) {
2223                nullLayouts();
2224                requestLayout();
2225                invalidate();
2226            }
2227        }
2228    }
2229
2230    /**
2231     * Sets the typeface and style in which the text should be displayed.
2232     * Note that not all Typeface families actually have bold and italic
2233     * variants, so you may need to use
2234     * {@link #setTypeface(Typeface, int)} to get the appearance
2235     * that you actually want.
2236     *
2237     * @attr ref android.R.styleable#TextView_typeface
2238     * @attr ref android.R.styleable#TextView_textStyle
2239     */
2240    public void setTypeface(Typeface tf) {
2241        if (mTextPaint.getTypeface() != tf) {
2242            mTextPaint.setTypeface(tf);
2243
2244            if (mLayout != null) {
2245                nullLayouts();
2246                requestLayout();
2247                invalidate();
2248            }
2249        }
2250    }
2251
2252    /**
2253     * @return the current typeface and style in which the text is being
2254     * displayed.
2255     */
2256    public Typeface getTypeface() {
2257        return mTextPaint.getTypeface();
2258    }
2259
2260    /**
2261     * Sets the text color for all the states (normal, selected,
2262     * focused) to be this color.
2263     *
2264     * @attr ref android.R.styleable#TextView_textColor
2265     */
2266    @android.view.RemotableViewMethod
2267    public void setTextColor(int color) {
2268        mTextColor = ColorStateList.valueOf(color);
2269        updateTextColors();
2270    }
2271
2272    /**
2273     * Sets the text color.
2274     *
2275     * @attr ref android.R.styleable#TextView_textColor
2276     */
2277    public void setTextColor(ColorStateList colors) {
2278        if (colors == null) {
2279            throw new NullPointerException();
2280        }
2281
2282        mTextColor = colors;
2283        updateTextColors();
2284    }
2285
2286    /**
2287     * Return the set of text colors.
2288     *
2289     * @return Returns the set of text colors.
2290     */
2291    public final ColorStateList getTextColors() {
2292        return mTextColor;
2293    }
2294
2295    /**
2296     * <p>Return the current color selected for normal text.</p>
2297     *
2298     * @return Returns the current text color.
2299     */
2300    public final int getCurrentTextColor() {
2301        return mCurTextColor;
2302    }
2303
2304    /**
2305     * Sets the color used to display the selection highlight.
2306     *
2307     * @attr ref android.R.styleable#TextView_textColorHighlight
2308     */
2309    @android.view.RemotableViewMethod
2310    public void setHighlightColor(int color) {
2311        if (mHighlightColor != color) {
2312            mHighlightColor = color;
2313            invalidate();
2314        }
2315    }
2316
2317    /**
2318     * Gives the text a shadow of the specified radius and color, the specified
2319     * distance from its normal position.
2320     *
2321     * @attr ref android.R.styleable#TextView_shadowColor
2322     * @attr ref android.R.styleable#TextView_shadowDx
2323     * @attr ref android.R.styleable#TextView_shadowDy
2324     * @attr ref android.R.styleable#TextView_shadowRadius
2325     */
2326    public void setShadowLayer(float radius, float dx, float dy, int color) {
2327        mTextPaint.setShadowLayer(radius, dx, dy, color);
2328
2329        mShadowRadius = radius;
2330        mShadowDx = dx;
2331        mShadowDy = dy;
2332
2333        invalidate();
2334    }
2335
2336    /**
2337     * @return the base paint used for the text.  Please use this only to
2338     * consult the Paint's properties and not to change them.
2339     */
2340    public TextPaint getPaint() {
2341        return mTextPaint;
2342    }
2343
2344    /**
2345     * Sets the autolink mask of the text.  See {@link
2346     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2347     * possible values.
2348     *
2349     * @attr ref android.R.styleable#TextView_autoLink
2350     */
2351    @android.view.RemotableViewMethod
2352    public final void setAutoLinkMask(int mask) {
2353        mAutoLinkMask = mask;
2354    }
2355
2356    /**
2357     * Sets whether the movement method will automatically be set to
2358     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2359     * set to nonzero and links are detected in {@link #setText}.
2360     * The default is true.
2361     *
2362     * @attr ref android.R.styleable#TextView_linksClickable
2363     */
2364    @android.view.RemotableViewMethod
2365    public final void setLinksClickable(boolean whether) {
2366        mLinksClickable = whether;
2367    }
2368
2369    /**
2370     * Returns whether the movement method will automatically be set to
2371     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2372     * set to nonzero and links are detected in {@link #setText}.
2373     * The default is true.
2374     *
2375     * @attr ref android.R.styleable#TextView_linksClickable
2376     */
2377    public final boolean getLinksClickable() {
2378        return mLinksClickable;
2379    }
2380
2381    /**
2382     * Returns the list of URLSpans attached to the text
2383     * (by {@link Linkify} or otherwise) if any.  You can call
2384     * {@link URLSpan#getURL} on them to find where they link to
2385     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2386     * to find the region of the text they are attached to.
2387     */
2388    public URLSpan[] getUrls() {
2389        if (mText instanceof Spanned) {
2390            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2391        } else {
2392            return new URLSpan[0];
2393        }
2394    }
2395
2396    /**
2397     * Sets the color of the hint text.
2398     *
2399     * @attr ref android.R.styleable#TextView_textColorHint
2400     */
2401    @android.view.RemotableViewMethod
2402    public final void setHintTextColor(int color) {
2403        mHintTextColor = ColorStateList.valueOf(color);
2404        updateTextColors();
2405    }
2406
2407    /**
2408     * Sets the color of the hint text.
2409     *
2410     * @attr ref android.R.styleable#TextView_textColorHint
2411     */
2412    public final void setHintTextColor(ColorStateList colors) {
2413        mHintTextColor = colors;
2414        updateTextColors();
2415    }
2416
2417    /**
2418     * <p>Return the color used to paint the hint text.</p>
2419     *
2420     * @return Returns the list of hint text colors.
2421     */
2422    public final ColorStateList getHintTextColors() {
2423        return mHintTextColor;
2424    }
2425
2426    /**
2427     * <p>Return the current color selected to paint the hint text.</p>
2428     *
2429     * @return Returns the current hint text color.
2430     */
2431    public final int getCurrentHintTextColor() {
2432        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2433    }
2434
2435    /**
2436     * Sets the color of links in the text.
2437     *
2438     * @attr ref android.R.styleable#TextView_textColorLink
2439     */
2440    @android.view.RemotableViewMethod
2441    public final void setLinkTextColor(int color) {
2442        mLinkTextColor = ColorStateList.valueOf(color);
2443        updateTextColors();
2444    }
2445
2446    /**
2447     * Sets the color of links in the text.
2448     *
2449     * @attr ref android.R.styleable#TextView_textColorLink
2450     */
2451    public final void setLinkTextColor(ColorStateList colors) {
2452        mLinkTextColor = colors;
2453        updateTextColors();
2454    }
2455
2456    /**
2457     * <p>Returns the color used to paint links in the text.</p>
2458     *
2459     * @return Returns the list of link text colors.
2460     */
2461    public final ColorStateList getLinkTextColors() {
2462        return mLinkTextColor;
2463    }
2464
2465    /**
2466     * Sets the horizontal alignment of the text and the
2467     * vertical gravity that will be used when there is extra space
2468     * in the TextView beyond what is required for the text itself.
2469     *
2470     * @see android.view.Gravity
2471     * @attr ref android.R.styleable#TextView_gravity
2472     */
2473    public void setGravity(int gravity) {
2474        if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
2475            gravity |= Gravity.START;
2476        }
2477        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2478            gravity |= Gravity.TOP;
2479        }
2480
2481        boolean newLayout = false;
2482
2483        if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2484            (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
2485            newLayout = true;
2486        }
2487
2488        if (gravity != mGravity) {
2489            invalidate();
2490            mLayoutAlignment = null;
2491        }
2492
2493        mGravity = gravity;
2494
2495        if (mLayout != null && newLayout) {
2496            // XXX this is heavy-handed because no actual content changes.
2497            int want = mLayout.getWidth();
2498            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2499
2500            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2501                          mRight - mLeft - getCompoundPaddingLeft() -
2502                          getCompoundPaddingRight(), true);
2503        }
2504    }
2505
2506    /**
2507     * Returns the horizontal and vertical alignment of this TextView.
2508     *
2509     * @see android.view.Gravity
2510     * @attr ref android.R.styleable#TextView_gravity
2511     */
2512    public int getGravity() {
2513        return mGravity;
2514    }
2515
2516    /**
2517     * @return the flags on the Paint being used to display the text.
2518     * @see Paint#getFlags
2519     */
2520    public int getPaintFlags() {
2521        return mTextPaint.getFlags();
2522    }
2523
2524    /**
2525     * Sets flags on the Paint being used to display the text and
2526     * reflows the text if they are different from the old flags.
2527     * @see Paint#setFlags
2528     */
2529    @android.view.RemotableViewMethod
2530    public void setPaintFlags(int flags) {
2531        if (mTextPaint.getFlags() != flags) {
2532            mTextPaint.setFlags(flags);
2533
2534            if (mLayout != null) {
2535                nullLayouts();
2536                requestLayout();
2537                invalidate();
2538            }
2539        }
2540    }
2541
2542    /**
2543     * Sets whether the text should be allowed to be wider than the
2544     * View is.  If false, it will be wrapped to the width of the View.
2545     *
2546     * @attr ref android.R.styleable#TextView_scrollHorizontally
2547     */
2548    public void setHorizontallyScrolling(boolean whether) {
2549        if (mHorizontallyScrolling != whether) {
2550            mHorizontallyScrolling = whether;
2551
2552            if (mLayout != null) {
2553                nullLayouts();
2554                requestLayout();
2555                invalidate();
2556            }
2557        }
2558    }
2559
2560    /**
2561     * Returns whether the text is allowed to be wider than the View is.
2562     * If false, the text will be wrapped to the width of the View.
2563     *
2564     * @attr ref android.R.styleable#TextView_scrollHorizontally
2565     * @hide
2566     */
2567    public boolean getHorizontallyScrolling() {
2568        return mHorizontallyScrolling;
2569    }
2570
2571    /**
2572     * Makes the TextView at least this many lines tall.
2573     *
2574     * Setting this value overrides any other (minimum) height setting. A single line TextView will
2575     * set this value to 1.
2576     *
2577     * @attr ref android.R.styleable#TextView_minLines
2578     */
2579    @android.view.RemotableViewMethod
2580    public void setMinLines(int minlines) {
2581        mMinimum = minlines;
2582        mMinMode = LINES;
2583
2584        requestLayout();
2585        invalidate();
2586    }
2587
2588    /**
2589     * Makes the TextView at least this many pixels tall.
2590     *
2591     * Setting this value overrides any other (minimum) number of lines setting.
2592     *
2593     * @attr ref android.R.styleable#TextView_minHeight
2594     */
2595    @android.view.RemotableViewMethod
2596    public void setMinHeight(int minHeight) {
2597        mMinimum = minHeight;
2598        mMinMode = PIXELS;
2599
2600        requestLayout();
2601        invalidate();
2602    }
2603
2604    /**
2605     * Makes the TextView at most this many lines tall.
2606     *
2607     * Setting this value overrides any other (maximum) height setting.
2608     *
2609     * @attr ref android.R.styleable#TextView_maxLines
2610     */
2611    @android.view.RemotableViewMethod
2612    public void setMaxLines(int maxlines) {
2613        mMaximum = maxlines;
2614        mMaxMode = LINES;
2615
2616        requestLayout();
2617        invalidate();
2618    }
2619
2620    /**
2621     * Makes the TextView at most this many pixels tall.  This option is mutually exclusive with the
2622     * {@link #setMaxLines(int)} method.
2623     *
2624     * Setting this value overrides any other (maximum) number of lines setting.
2625     *
2626     * @attr ref android.R.styleable#TextView_maxHeight
2627     */
2628    @android.view.RemotableViewMethod
2629    public void setMaxHeight(int maxHeight) {
2630        mMaximum = maxHeight;
2631        mMaxMode = PIXELS;
2632
2633        requestLayout();
2634        invalidate();
2635    }
2636
2637    /**
2638     * Makes the TextView exactly this many lines tall.
2639     *
2640     * Note that setting this value overrides any other (minimum / maximum) number of lines or
2641     * height setting. A single line TextView will set this value to 1.
2642     *
2643     * @attr ref android.R.styleable#TextView_lines
2644     */
2645    @android.view.RemotableViewMethod
2646    public void setLines(int lines) {
2647        mMaximum = mMinimum = lines;
2648        mMaxMode = mMinMode = LINES;
2649
2650        requestLayout();
2651        invalidate();
2652    }
2653
2654    /**
2655     * Makes the TextView exactly this many pixels tall.
2656     * You could do the same thing by specifying this number in the
2657     * LayoutParams.
2658     *
2659     * Note that setting this value overrides any other (minimum / maximum) number of lines or
2660     * height setting.
2661     *
2662     * @attr ref android.R.styleable#TextView_height
2663     */
2664    @android.view.RemotableViewMethod
2665    public void setHeight(int pixels) {
2666        mMaximum = mMinimum = pixels;
2667        mMaxMode = mMinMode = PIXELS;
2668
2669        requestLayout();
2670        invalidate();
2671    }
2672
2673    /**
2674     * Makes the TextView at least this many ems wide
2675     *
2676     * @attr ref android.R.styleable#TextView_minEms
2677     */
2678    @android.view.RemotableViewMethod
2679    public void setMinEms(int minems) {
2680        mMinWidth = minems;
2681        mMinWidthMode = EMS;
2682
2683        requestLayout();
2684        invalidate();
2685    }
2686
2687    /**
2688     * Makes the TextView at least this many pixels wide
2689     *
2690     * @attr ref android.R.styleable#TextView_minWidth
2691     */
2692    @android.view.RemotableViewMethod
2693    public void setMinWidth(int minpixels) {
2694        mMinWidth = minpixels;
2695        mMinWidthMode = PIXELS;
2696
2697        requestLayout();
2698        invalidate();
2699    }
2700
2701    /**
2702     * Makes the TextView at most this many ems wide
2703     *
2704     * @attr ref android.R.styleable#TextView_maxEms
2705     */
2706    @android.view.RemotableViewMethod
2707    public void setMaxEms(int maxems) {
2708        mMaxWidth = maxems;
2709        mMaxWidthMode = EMS;
2710
2711        requestLayout();
2712        invalidate();
2713    }
2714
2715    /**
2716     * Makes the TextView at most this many pixels wide
2717     *
2718     * @attr ref android.R.styleable#TextView_maxWidth
2719     */
2720    @android.view.RemotableViewMethod
2721    public void setMaxWidth(int maxpixels) {
2722        mMaxWidth = maxpixels;
2723        mMaxWidthMode = PIXELS;
2724
2725        requestLayout();
2726        invalidate();
2727    }
2728
2729    /**
2730     * Makes the TextView exactly this many ems wide
2731     *
2732     * @attr ref android.R.styleable#TextView_ems
2733     */
2734    @android.view.RemotableViewMethod
2735    public void setEms(int ems) {
2736        mMaxWidth = mMinWidth = ems;
2737        mMaxWidthMode = mMinWidthMode = EMS;
2738
2739        requestLayout();
2740        invalidate();
2741    }
2742
2743    /**
2744     * Makes the TextView exactly this many pixels wide.
2745     * You could do the same thing by specifying this number in the
2746     * LayoutParams.
2747     *
2748     * @attr ref android.R.styleable#TextView_width
2749     */
2750    @android.view.RemotableViewMethod
2751    public void setWidth(int pixels) {
2752        mMaxWidth = mMinWidth = pixels;
2753        mMaxWidthMode = mMinWidthMode = PIXELS;
2754
2755        requestLayout();
2756        invalidate();
2757    }
2758
2759
2760    /**
2761     * Sets line spacing for this TextView.  Each line will have its height
2762     * multiplied by <code>mult</code> and have <code>add</code> added to it.
2763     *
2764     * @attr ref android.R.styleable#TextView_lineSpacingExtra
2765     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
2766     */
2767    public void setLineSpacing(float add, float mult) {
2768        if (mSpacingAdd != add || mSpacingMult != mult) {
2769            mSpacingAdd = add;
2770            mSpacingMult = mult;
2771
2772            if (mLayout != null) {
2773                nullLayouts();
2774                requestLayout();
2775                invalidate();
2776            }
2777        }
2778    }
2779
2780    /**
2781     * Convenience method: Append the specified text to the TextView's
2782     * display buffer, upgrading it to BufferType.EDITABLE if it was
2783     * not already editable.
2784     */
2785    public final void append(CharSequence text) {
2786        append(text, 0, text.length());
2787    }
2788
2789    /**
2790     * Convenience method: Append the specified text slice to the TextView's
2791     * display buffer, upgrading it to BufferType.EDITABLE if it was
2792     * not already editable.
2793     */
2794    public void append(CharSequence text, int start, int end) {
2795        if (!(mText instanceof Editable)) {
2796            setText(mText, BufferType.EDITABLE);
2797        }
2798
2799        ((Editable) mText).append(text, start, end);
2800    }
2801
2802    private void updateTextColors() {
2803        boolean inval = false;
2804        int color = mTextColor.getColorForState(getDrawableState(), 0);
2805        if (color != mCurTextColor) {
2806            mCurTextColor = color;
2807            inval = true;
2808        }
2809        if (mLinkTextColor != null) {
2810            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
2811            if (color != mTextPaint.linkColor) {
2812                mTextPaint.linkColor = color;
2813                inval = true;
2814            }
2815        }
2816        if (mHintTextColor != null) {
2817            color = mHintTextColor.getColorForState(getDrawableState(), 0);
2818            if (color != mCurHintTextColor && mText.length() == 0) {
2819                mCurHintTextColor = color;
2820                inval = true;
2821            }
2822        }
2823        if (inval) {
2824            invalidate();
2825        }
2826    }
2827
2828    @Override
2829    protected void drawableStateChanged() {
2830        super.drawableStateChanged();
2831        if (mTextColor != null && mTextColor.isStateful()
2832                || (mHintTextColor != null && mHintTextColor.isStateful())
2833                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
2834            updateTextColors();
2835        }
2836
2837        final Drawables dr = mDrawables;
2838        if (dr != null) {
2839            int[] state = getDrawableState();
2840            if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
2841                dr.mDrawableTop.setState(state);
2842            }
2843            if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
2844                dr.mDrawableBottom.setState(state);
2845            }
2846            if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
2847                dr.mDrawableLeft.setState(state);
2848            }
2849            if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
2850                dr.mDrawableRight.setState(state);
2851            }
2852            if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
2853                dr.mDrawableStart.setState(state);
2854            }
2855            if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
2856                dr.mDrawableEnd.setState(state);
2857            }
2858        }
2859    }
2860
2861    /**
2862     * User interface state that is stored by TextView for implementing
2863     * {@link View#onSaveInstanceState}.
2864     */
2865    public static class SavedState extends BaseSavedState {
2866        int selStart;
2867        int selEnd;
2868        CharSequence text;
2869        boolean frozenWithFocus;
2870        CharSequence error;
2871
2872        SavedState(Parcelable superState) {
2873            super(superState);
2874        }
2875
2876        @Override
2877        public void writeToParcel(Parcel out, int flags) {
2878            super.writeToParcel(out, flags);
2879            out.writeInt(selStart);
2880            out.writeInt(selEnd);
2881            out.writeInt(frozenWithFocus ? 1 : 0);
2882            TextUtils.writeToParcel(text, out, flags);
2883
2884            if (error == null) {
2885                out.writeInt(0);
2886            } else {
2887                out.writeInt(1);
2888                TextUtils.writeToParcel(error, out, flags);
2889            }
2890        }
2891
2892        @Override
2893        public String toString() {
2894            String str = "TextView.SavedState{"
2895                    + Integer.toHexString(System.identityHashCode(this))
2896                    + " start=" + selStart + " end=" + selEnd;
2897            if (text != null) {
2898                str += " text=" + text;
2899            }
2900            return str + "}";
2901        }
2902
2903        @SuppressWarnings("hiding")
2904        public static final Parcelable.Creator<SavedState> CREATOR
2905                = new Parcelable.Creator<SavedState>() {
2906            public SavedState createFromParcel(Parcel in) {
2907                return new SavedState(in);
2908            }
2909
2910            public SavedState[] newArray(int size) {
2911                return new SavedState[size];
2912            }
2913        };
2914
2915        private SavedState(Parcel in) {
2916            super(in);
2917            selStart = in.readInt();
2918            selEnd = in.readInt();
2919            frozenWithFocus = (in.readInt() != 0);
2920            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2921
2922            if (in.readInt() != 0) {
2923                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2924            }
2925        }
2926    }
2927
2928    @Override
2929    public Parcelable onSaveInstanceState() {
2930        Parcelable superState = super.onSaveInstanceState();
2931
2932        // Save state if we are forced to
2933        boolean save = mFreezesText;
2934        int start = 0;
2935        int end = 0;
2936
2937        if (mText != null) {
2938            start = getSelectionStart();
2939            end = getSelectionEnd();
2940            if (start >= 0 || end >= 0) {
2941                // Or save state if there is a selection
2942                save = true;
2943            }
2944        }
2945
2946        if (save) {
2947            SavedState ss = new SavedState(superState);
2948            // XXX Should also save the current scroll position!
2949            ss.selStart = start;
2950            ss.selEnd = end;
2951
2952            if (mText instanceof Spanned) {
2953                /*
2954                 * Calling setText() strips off any ChangeWatchers;
2955                 * strip them now to avoid leaking references.
2956                 * But do it to a copy so that if there are any
2957                 * further changes to the text of this view, it
2958                 * won't get into an inconsistent state.
2959                 */
2960
2961                Spannable sp = new SpannableString(mText);
2962
2963                for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2964                    sp.removeSpan(cw);
2965                }
2966
2967                removeMisspelledSpans(sp);
2968                sp.removeSpan(mSuggestionRangeSpan);
2969
2970                ss.text = sp;
2971            } else {
2972                ss.text = mText.toString();
2973            }
2974
2975            if (isFocused() && start >= 0 && end >= 0) {
2976                ss.frozenWithFocus = true;
2977            }
2978
2979            ss.error = mError;
2980
2981            return ss;
2982        }
2983
2984        return superState;
2985    }
2986
2987    void removeMisspelledSpans(Spannable spannable) {
2988        SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
2989                SuggestionSpan.class);
2990        for (int i = 0; i < suggestionSpans.length; i++) {
2991            int flags = suggestionSpans[i].getFlags();
2992            if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
2993                    && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
2994                spannable.removeSpan(suggestionSpans[i]);
2995            }
2996        }
2997    }
2998
2999    @Override
3000    public void onRestoreInstanceState(Parcelable state) {
3001        if (!(state instanceof SavedState)) {
3002            super.onRestoreInstanceState(state);
3003            return;
3004        }
3005
3006        SavedState ss = (SavedState)state;
3007        super.onRestoreInstanceState(ss.getSuperState());
3008
3009        // XXX restore buffer type too, as well as lots of other stuff
3010        if (ss.text != null) {
3011            setText(ss.text);
3012        }
3013
3014        if (ss.selStart >= 0 && ss.selEnd >= 0) {
3015            if (mText instanceof Spannable) {
3016                int len = mText.length();
3017
3018                if (ss.selStart > len || ss.selEnd > len) {
3019                    String restored = "";
3020
3021                    if (ss.text != null) {
3022                        restored = "(restored) ";
3023                    }
3024
3025                    Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
3026                          "/" + ss.selEnd + " out of range for " + restored +
3027                          "text " + mText);
3028                } else {
3029                    Selection.setSelection((Spannable) mText, ss.selStart,
3030                                           ss.selEnd);
3031
3032                    if (ss.frozenWithFocus) {
3033                        mFrozenWithFocus = true;
3034                    }
3035                }
3036            }
3037        }
3038
3039        if (ss.error != null) {
3040            final CharSequence error = ss.error;
3041            // Display the error later, after the first layout pass
3042            post(new Runnable() {
3043                public void run() {
3044                    setError(error);
3045                }
3046            });
3047        }
3048    }
3049
3050    /**
3051     * Control whether this text view saves its entire text contents when
3052     * freezing to an icicle, in addition to dynamic state such as cursor
3053     * position.  By default this is false, not saving the text.  Set to true
3054     * if the text in the text view is not being saved somewhere else in
3055     * persistent storage (such as in a content provider) so that if the
3056     * view is later thawed the user will not lose their data.
3057     *
3058     * @param freezesText Controls whether a frozen icicle should include the
3059     * entire text data: true to include it, false to not.
3060     *
3061     * @attr ref android.R.styleable#TextView_freezesText
3062     */
3063    @android.view.RemotableViewMethod
3064    public void setFreezesText(boolean freezesText) {
3065        mFreezesText = freezesText;
3066    }
3067
3068    /**
3069     * Return whether this text view is including its entire text contents
3070     * in frozen icicles.
3071     *
3072     * @return Returns true if text is included, false if it isn't.
3073     *
3074     * @see #setFreezesText
3075     */
3076    public boolean getFreezesText() {
3077        return mFreezesText;
3078    }
3079
3080    ///////////////////////////////////////////////////////////////////////////
3081
3082    /**
3083     * Sets the Factory used to create new Editables.
3084     */
3085    public final void setEditableFactory(Editable.Factory factory) {
3086        mEditableFactory = factory;
3087        setText(mText);
3088    }
3089
3090    /**
3091     * Sets the Factory used to create new Spannables.
3092     */
3093    public final void setSpannableFactory(Spannable.Factory factory) {
3094        mSpannableFactory = factory;
3095        setText(mText);
3096    }
3097
3098    /**
3099     * Sets the string value of the TextView. TextView <em>does not</em> accept
3100     * HTML-like formatting, which you can do with text strings in XML resource files.
3101     * To style your strings, attach android.text.style.* objects to a
3102     * {@link android.text.SpannableString SpannableString}, or see the
3103     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
3104     * Available Resource Types</a> documentation for an example of setting
3105     * formatted text in the XML resource file.
3106     *
3107     * @attr ref android.R.styleable#TextView_text
3108     */
3109    @android.view.RemotableViewMethod
3110    public final void setText(CharSequence text) {
3111        setText(text, mBufferType);
3112    }
3113
3114    /**
3115     * Like {@link #setText(CharSequence)},
3116     * except that the cursor position (if any) is retained in the new text.
3117     *
3118     * @param text The new text to place in the text view.
3119     *
3120     * @see #setText(CharSequence)
3121     */
3122    @android.view.RemotableViewMethod
3123    public final void setTextKeepState(CharSequence text) {
3124        setTextKeepState(text, mBufferType);
3125    }
3126
3127    /**
3128     * Sets the text that this TextView is to display (see
3129     * {@link #setText(CharSequence)}) and also sets whether it is stored
3130     * in a styleable/spannable buffer and whether it is editable.
3131     *
3132     * @attr ref android.R.styleable#TextView_text
3133     * @attr ref android.R.styleable#TextView_bufferType
3134     */
3135    public void setText(CharSequence text, BufferType type) {
3136        setText(text, type, true, 0);
3137
3138        if (mCharWrapper != null) {
3139            mCharWrapper.mChars = null;
3140        }
3141    }
3142
3143    private void setText(CharSequence text, BufferType type,
3144                         boolean notifyBefore, int oldlen) {
3145        if (text == null) {
3146            text = "";
3147        }
3148
3149        // If suggestions are not enabled, remove the suggestion spans from the text
3150        if (!isSuggestionsEnabled()) {
3151            text = removeSuggestionSpans(text);
3152        }
3153
3154        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3155
3156        if (text instanceof Spanned &&
3157            ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
3158            if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3159                setHorizontalFadingEdgeEnabled(true);
3160                mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3161            } else {
3162                setHorizontalFadingEdgeEnabled(false);
3163                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3164            }
3165            setEllipsize(TextUtils.TruncateAt.MARQUEE);
3166        }
3167
3168        int n = mFilters.length;
3169        for (int i = 0; i < n; i++) {
3170            CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
3171            if (out != null) {
3172                text = out;
3173            }
3174        }
3175
3176        if (notifyBefore) {
3177            if (mText != null) {
3178                oldlen = mText.length();
3179                sendBeforeTextChanged(mText, 0, oldlen, text.length());
3180            } else {
3181                sendBeforeTextChanged("", 0, 0, text.length());
3182            }
3183        }
3184
3185        boolean needEditableForNotification = false;
3186
3187        if (mListeners != null && mListeners.size() != 0) {
3188            needEditableForNotification = true;
3189        }
3190
3191        if (type == BufferType.EDITABLE || mInput != null || needEditableForNotification) {
3192            Editable t = mEditableFactory.newEditable(text);
3193            text = t;
3194            setFilters(t, mFilters);
3195            InputMethodManager imm = InputMethodManager.peekInstance();
3196            if (imm != null) imm.restartInput(this);
3197        } else if (type == BufferType.SPANNABLE || mMovement != null) {
3198            text = mSpannableFactory.newSpannable(text);
3199        } else if (!(text instanceof CharWrapper)) {
3200            text = TextUtils.stringOrSpannedString(text);
3201        }
3202
3203        if (mAutoLinkMask != 0) {
3204            Spannable s2;
3205
3206            if (type == BufferType.EDITABLE || text instanceof Spannable) {
3207                s2 = (Spannable) text;
3208            } else {
3209                s2 = mSpannableFactory.newSpannable(text);
3210            }
3211
3212            if (Linkify.addLinks(s2, mAutoLinkMask)) {
3213                text = s2;
3214                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3215
3216                /*
3217                 * We must go ahead and set the text before changing the
3218                 * movement method, because setMovementMethod() may call
3219                 * setText() again to try to upgrade the buffer type.
3220                 */
3221                mText = text;
3222
3223                // Do not change the movement method for text that support text selection as it
3224                // would prevent an arbitrary cursor displacement.
3225                if (mLinksClickable && !textCanBeSelected()) {
3226                    setMovementMethod(LinkMovementMethod.getInstance());
3227                }
3228            }
3229        }
3230
3231        mBufferType = type;
3232        mText = text;
3233
3234        if (mTransformation == null) {
3235            mTransformed = text;
3236        } else {
3237            mTransformed = mTransformation.getTransformation(text, this);
3238        }
3239
3240        final int textLength = text.length();
3241
3242        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
3243            Spannable sp = (Spannable) text;
3244
3245            // Remove any ChangeWatchers that might have come
3246            // from other TextViews.
3247            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3248            final int count = watchers.length;
3249            for (int i = 0; i < count; i++)
3250                sp.removeSpan(watchers[i]);
3251
3252            if (mChangeWatcher == null)
3253                mChangeWatcher = new ChangeWatcher();
3254
3255            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
3256                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
3257
3258            if (mInput != null) {
3259                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3260            }
3261
3262            if (mTransformation != null) {
3263                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3264            }
3265
3266            if (mMovement != null) {
3267                mMovement.initialize(this, (Spannable) text);
3268
3269                /*
3270                 * Initializing the movement method will have set the
3271                 * selection, so reset mSelectionMoved to keep that from
3272                 * interfering with the normal on-focus selection-setting.
3273                 */
3274                mSelectionMoved = false;
3275            }
3276        }
3277
3278        if (mLayout != null) {
3279            checkForRelayout();
3280        }
3281
3282        sendOnTextChanged(text, 0, oldlen, textLength);
3283        onTextChanged(text, 0, oldlen, textLength);
3284
3285        if (needEditableForNotification) {
3286            sendAfterTextChanged((Editable) text);
3287        }
3288
3289        // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
3290        prepareCursorControllers();
3291    }
3292
3293    /**
3294     * Sets the TextView to display the specified slice of the specified
3295     * char array.  You must promise that you will not change the contents
3296     * of the array except for right before another call to setText(),
3297     * since the TextView has no way to know that the text
3298     * has changed and that it needs to invalidate and re-layout.
3299     */
3300    public final void setText(char[] text, int start, int len) {
3301        int oldlen = 0;
3302
3303        if (start < 0 || len < 0 || start + len > text.length) {
3304            throw new IndexOutOfBoundsException(start + ", " + len);
3305        }
3306
3307        /*
3308         * We must do the before-notification here ourselves because if
3309         * the old text is a CharWrapper we destroy it before calling
3310         * into the normal path.
3311         */
3312        if (mText != null) {
3313            oldlen = mText.length();
3314            sendBeforeTextChanged(mText, 0, oldlen, len);
3315        } else {
3316            sendBeforeTextChanged("", 0, 0, len);
3317        }
3318
3319        if (mCharWrapper == null) {
3320            mCharWrapper = new CharWrapper(text, start, len);
3321        } else {
3322            mCharWrapper.set(text, start, len);
3323        }
3324
3325        setText(mCharWrapper, mBufferType, false, oldlen);
3326    }
3327
3328    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
3329        private char[] mChars;
3330        private int mStart, mLength;
3331
3332        public CharWrapper(char[] chars, int start, int len) {
3333            mChars = chars;
3334            mStart = start;
3335            mLength = len;
3336        }
3337
3338        /* package */ void set(char[] chars, int start, int len) {
3339            mChars = chars;
3340            mStart = start;
3341            mLength = len;
3342        }
3343
3344        public int length() {
3345            return mLength;
3346        }
3347
3348        public char charAt(int off) {
3349            return mChars[off + mStart];
3350        }
3351
3352        @Override
3353        public String toString() {
3354            return new String(mChars, mStart, mLength);
3355        }
3356
3357        public CharSequence subSequence(int start, int end) {
3358            if (start < 0 || end < 0 || start > mLength || end > mLength) {
3359                throw new IndexOutOfBoundsException(start + ", " + end);
3360            }
3361
3362            return new String(mChars, start + mStart, end - start);
3363        }
3364
3365        public void getChars(int start, int end, char[] buf, int off) {
3366            if (start < 0 || end < 0 || start > mLength || end > mLength) {
3367                throw new IndexOutOfBoundsException(start + ", " + end);
3368            }
3369
3370            System.arraycopy(mChars, start + mStart, buf, off, end - start);
3371        }
3372
3373        public void drawText(Canvas c, int start, int end,
3374                             float x, float y, Paint p) {
3375            c.drawText(mChars, start + mStart, end - start, x, y, p);
3376        }
3377
3378        public void drawTextRun(Canvas c, int start, int end,
3379                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
3380            int count = end - start;
3381            int contextCount = contextEnd - contextStart;
3382            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
3383                    contextCount, x, y, flags, p);
3384        }
3385
3386        public float measureText(int start, int end, Paint p) {
3387            return p.measureText(mChars, start + mStart, end - start);
3388        }
3389
3390        public int getTextWidths(int start, int end, float[] widths, Paint p) {
3391            return p.getTextWidths(mChars, start + mStart, end - start, widths);
3392        }
3393
3394        public float getTextRunAdvances(int start, int end, int contextStart,
3395                int contextEnd, int flags, float[] advances, int advancesIndex,
3396                Paint p) {
3397            int count = end - start;
3398            int contextCount = contextEnd - contextStart;
3399            return p.getTextRunAdvances(mChars, start + mStart, count,
3400                    contextStart + mStart, contextCount, flags, advances,
3401                    advancesIndex);
3402        }
3403
3404        public float getTextRunAdvances(int start, int end, int contextStart,
3405                int contextEnd, int flags, float[] advances, int advancesIndex,
3406                Paint p, int reserved) {
3407            int count = end - start;
3408            int contextCount = contextEnd - contextStart;
3409            return p.getTextRunAdvances(mChars, start + mStart, count,
3410                    contextStart + mStart, contextCount, flags, advances,
3411                    advancesIndex, reserved);
3412        }
3413
3414        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
3415                int offset, int cursorOpt, Paint p) {
3416            int contextCount = contextEnd - contextStart;
3417            return p.getTextRunCursor(mChars, contextStart + mStart,
3418                    contextCount, flags, offset + mStart, cursorOpt);
3419        }
3420    }
3421
3422    /**
3423     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3424     * except that the cursor position (if any) is retained in the new text.
3425     *
3426     * @see #setText(CharSequence, android.widget.TextView.BufferType)
3427     */
3428    public final void setTextKeepState(CharSequence text, BufferType type) {
3429        int start = getSelectionStart();
3430        int end = getSelectionEnd();
3431        int len = text.length();
3432
3433        setText(text, type);
3434
3435        if (start >= 0 || end >= 0) {
3436            if (mText instanceof Spannable) {
3437                Selection.setSelection((Spannable) mText,
3438                                       Math.max(0, Math.min(start, len)),
3439                                       Math.max(0, Math.min(end, len)));
3440            }
3441        }
3442    }
3443
3444    @android.view.RemotableViewMethod
3445    public final void setText(int resid) {
3446        setText(getContext().getResources().getText(resid));
3447    }
3448
3449    public final void setText(int resid, BufferType type) {
3450        setText(getContext().getResources().getText(resid), type);
3451    }
3452
3453    /**
3454     * Sets the text to be displayed when the text of the TextView is empty.
3455     * Null means to use the normal empty text. The hint does not currently
3456     * participate in determining the size of the view.
3457     *
3458     * @attr ref android.R.styleable#TextView_hint
3459     */
3460    @android.view.RemotableViewMethod
3461    public final void setHint(CharSequence hint) {
3462        mHint = TextUtils.stringOrSpannedString(hint);
3463
3464        if (mLayout != null) {
3465            checkForRelayout();
3466        }
3467
3468        if (mText.length() == 0) {
3469            invalidate();
3470        }
3471    }
3472
3473    /**
3474     * Sets the text to be displayed when the text of the TextView is empty,
3475     * from a resource.
3476     *
3477     * @attr ref android.R.styleable#TextView_hint
3478     */
3479    @android.view.RemotableViewMethod
3480    public final void setHint(int resid) {
3481        setHint(getContext().getResources().getText(resid));
3482    }
3483
3484    /**
3485     * Returns the hint that is displayed when the text of the TextView
3486     * is empty.
3487     *
3488     * @attr ref android.R.styleable#TextView_hint
3489     */
3490    @ViewDebug.CapturedViewProperty
3491    public CharSequence getHint() {
3492        return mHint;
3493    }
3494
3495    private static boolean isMultilineInputType(int type) {
3496        return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3497            (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3498    }
3499
3500    /**
3501     * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3502     * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3503     * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
3504     * then a soft keyboard will not be displayed for this text view.
3505     *
3506     * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3507     * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3508     * type.
3509     *
3510     * @see #getInputType()
3511     * @see #setRawInputType(int)
3512     * @see android.text.InputType
3513     * @attr ref android.R.styleable#TextView_inputType
3514     */
3515    public void setInputType(int type) {
3516        final boolean wasPassword = isPasswordInputType(mInputType);
3517        final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
3518        setInputType(type, false);
3519        final boolean isPassword = isPasswordInputType(type);
3520        final boolean isVisiblePassword = isVisiblePasswordInputType(type);
3521        boolean forceUpdate = false;
3522        if (isPassword) {
3523            setTransformationMethod(PasswordTransformationMethod.getInstance());
3524            setTypefaceByIndex(MONOSPACE, 0);
3525        } else if (isVisiblePassword) {
3526            if (mTransformation == PasswordTransformationMethod.getInstance()) {
3527                forceUpdate = true;
3528            }
3529            setTypefaceByIndex(MONOSPACE, 0);
3530        } else if (wasPassword || wasVisiblePassword) {
3531            // not in password mode, clean up typeface and transformation
3532            setTypefaceByIndex(-1, -1);
3533            if (mTransformation == PasswordTransformationMethod.getInstance()) {
3534                forceUpdate = true;
3535            }
3536        }
3537
3538        boolean singleLine = !isMultilineInputType(type);
3539
3540        // We need to update the single line mode if it has changed or we
3541        // were previously in password mode.
3542        if (mSingleLine != singleLine || forceUpdate) {
3543            // Change single line mode, but only change the transformation if
3544            // we are not in password mode.
3545            applySingleLine(singleLine, !isPassword, true);
3546        }
3547
3548        if (!isSuggestionsEnabled()) {
3549            mText = removeSuggestionSpans(mText);
3550        }
3551
3552        InputMethodManager imm = InputMethodManager.peekInstance();
3553        if (imm != null) imm.restartInput(this);
3554    }
3555
3556    /**
3557     * It would be better to rely on the input type for everything. A password inputType should have
3558     * a password transformation. We should hence use isPasswordInputType instead of this method.
3559     *
3560     * We should:
3561     * - Call setInputType in setKeyListener instead of changing the input type directly (which
3562     * would install the correct transformation).
3563     * - Refuse the installation of a non-password transformation in setTransformation if the input
3564     * type is password.
3565     *
3566     * However, this is like this for legacy reasons and we cannot break existing apps. This method
3567     * is useful since it matches what the user can see (obfuscated text or not).
3568     *
3569     * @return true if the current transformation method is of the password type.
3570     */
3571    private boolean hasPasswordTransformationMethod() {
3572        return mTransformation instanceof PasswordTransformationMethod;
3573    }
3574
3575    private static boolean isPasswordInputType(int inputType) {
3576        final int variation =
3577                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
3578        return variation
3579                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
3580                || variation
3581                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
3582                || variation
3583                == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
3584    }
3585
3586    private static boolean isVisiblePasswordInputType(int inputType) {
3587        final int variation =
3588                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
3589        return variation
3590                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
3591    }
3592
3593    /**
3594     * Directly change the content type integer of the text view, without
3595     * modifying any other state.
3596     * @see #setInputType(int)
3597     * @see android.text.InputType
3598     * @attr ref android.R.styleable#TextView_inputType
3599     */
3600    public void setRawInputType(int type) {
3601        mInputType = type;
3602    }
3603
3604    private void setInputType(int type, boolean direct) {
3605        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
3606        KeyListener input;
3607        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
3608            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
3609            TextKeyListener.Capitalize cap;
3610            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
3611                cap = TextKeyListener.Capitalize.CHARACTERS;
3612            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
3613                cap = TextKeyListener.Capitalize.WORDS;
3614            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
3615                cap = TextKeyListener.Capitalize.SENTENCES;
3616            } else {
3617                cap = TextKeyListener.Capitalize.NONE;
3618            }
3619            input = TextKeyListener.getInstance(autotext, cap);
3620        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
3621            input = DigitsKeyListener.getInstance(
3622                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
3623                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
3624        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
3625            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
3626                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
3627                    input = DateKeyListener.getInstance();
3628                    break;
3629                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
3630                    input = TimeKeyListener.getInstance();
3631                    break;
3632                default:
3633                    input = DateTimeKeyListener.getInstance();
3634                    break;
3635            }
3636        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
3637            input = DialerKeyListener.getInstance();
3638        } else {
3639            input = TextKeyListener.getInstance();
3640        }
3641        setRawInputType(type);
3642        if (direct) mInput = input;
3643        else {
3644            setKeyListenerOnly(input);
3645        }
3646    }
3647
3648    /**
3649     * Get the type of the content.
3650     *
3651     * @see #setInputType(int)
3652     * @see android.text.InputType
3653     */
3654    public int getInputType() {
3655        return mInputType;
3656    }
3657
3658    /**
3659     * Change the editor type integer associated with the text view, which
3660     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
3661     * has focus.
3662     * @see #getImeOptions
3663     * @see android.view.inputmethod.EditorInfo
3664     * @attr ref android.R.styleable#TextView_imeOptions
3665     */
3666    public void setImeOptions(int imeOptions) {
3667        if (mInputContentType == null) {
3668            mInputContentType = new InputContentType();
3669        }
3670        mInputContentType.imeOptions = imeOptions;
3671    }
3672
3673    /**
3674     * Get the type of the IME editor.
3675     *
3676     * @see #setImeOptions(int)
3677     * @see android.view.inputmethod.EditorInfo
3678     */
3679    public int getImeOptions() {
3680        return mInputContentType != null
3681                ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
3682    }
3683
3684    /**
3685     * Change the custom IME action associated with the text view, which
3686     * will be reported to an IME with {@link EditorInfo#actionLabel}
3687     * and {@link EditorInfo#actionId} when it has focus.
3688     * @see #getImeActionLabel
3689     * @see #getImeActionId
3690     * @see android.view.inputmethod.EditorInfo
3691     * @attr ref android.R.styleable#TextView_imeActionLabel
3692     * @attr ref android.R.styleable#TextView_imeActionId
3693     */
3694    public void setImeActionLabel(CharSequence label, int actionId) {
3695        if (mInputContentType == null) {
3696            mInputContentType = new InputContentType();
3697        }
3698        mInputContentType.imeActionLabel = label;
3699        mInputContentType.imeActionId = actionId;
3700    }
3701
3702    /**
3703     * Get the IME action label previous set with {@link #setImeActionLabel}.
3704     *
3705     * @see #setImeActionLabel
3706     * @see android.view.inputmethod.EditorInfo
3707     */
3708    public CharSequence getImeActionLabel() {
3709        return mInputContentType != null
3710                ? mInputContentType.imeActionLabel : null;
3711    }
3712
3713    /**
3714     * Get the IME action ID previous set with {@link #setImeActionLabel}.
3715     *
3716     * @see #setImeActionLabel
3717     * @see android.view.inputmethod.EditorInfo
3718     */
3719    public int getImeActionId() {
3720        return mInputContentType != null
3721                ? mInputContentType.imeActionId : 0;
3722    }
3723
3724    /**
3725     * Set a special listener to be called when an action is performed
3726     * on the text view.  This will be called when the enter key is pressed,
3727     * or when an action supplied to the IME is selected by the user.  Setting
3728     * this means that the normal hard key event will not insert a newline
3729     * into the text view, even if it is multi-line; holding down the ALT
3730     * modifier will, however, allow the user to insert a newline character.
3731     */
3732    public void setOnEditorActionListener(OnEditorActionListener l) {
3733        if (mInputContentType == null) {
3734            mInputContentType = new InputContentType();
3735        }
3736        mInputContentType.onEditorActionListener = l;
3737    }
3738
3739    /**
3740     * Called when an attached input method calls
3741     * {@link InputConnection#performEditorAction(int)
3742     * InputConnection.performEditorAction()}
3743     * for this text view.  The default implementation will call your action
3744     * listener supplied to {@link #setOnEditorActionListener}, or perform
3745     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
3746     * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
3747     * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
3748     * EditorInfo.IME_ACTION_DONE}.
3749     *
3750     * <p>For backwards compatibility, if no IME options have been set and the
3751     * text view would not normally advance focus on enter, then
3752     * the NEXT and DONE actions received here will be turned into an enter
3753     * key down/up pair to go through the normal key handling.
3754     *
3755     * @param actionCode The code of the action being performed.
3756     *
3757     * @see #setOnEditorActionListener
3758     */
3759    public void onEditorAction(int actionCode) {
3760        final InputContentType ict = mInputContentType;
3761        if (ict != null) {
3762            if (ict.onEditorActionListener != null) {
3763                if (ict.onEditorActionListener.onEditorAction(this,
3764                        actionCode, null)) {
3765                    return;
3766                }
3767            }
3768
3769            // This is the handling for some default action.
3770            // Note that for backwards compatibility we don't do this
3771            // default handling if explicit ime options have not been given,
3772            // instead turning this into the normal enter key codes that an
3773            // app may be expecting.
3774            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3775                View v = focusSearch(FOCUS_FORWARD);
3776                if (v != null) {
3777                    if (!v.requestFocus(FOCUS_FORWARD)) {
3778                        throw new IllegalStateException("focus search returned a view " +
3779                                "that wasn't able to take focus!");
3780                    }
3781                }
3782                return;
3783
3784            } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
3785                View v = focusSearch(FOCUS_BACKWARD);
3786                if (v != null) {
3787                    if (!v.requestFocus(FOCUS_BACKWARD)) {
3788                        throw new IllegalStateException("focus search returned a view " +
3789                                "that wasn't able to take focus!");
3790                    }
3791                }
3792                return;
3793
3794            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3795                InputMethodManager imm = InputMethodManager.peekInstance();
3796                if (imm != null && imm.isActive(this)) {
3797                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
3798                }
3799                return;
3800            }
3801        }
3802
3803        Handler h = getHandler();
3804        if (h != null) {
3805            long eventTime = SystemClock.uptimeMillis();
3806            h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
3807                    new KeyEvent(eventTime, eventTime,
3808                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
3809                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
3810                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3811                    | KeyEvent.FLAG_EDITOR_ACTION)));
3812            h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
3813                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
3814                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
3815                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
3816                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3817                    | KeyEvent.FLAG_EDITOR_ACTION)));
3818        }
3819    }
3820
3821    /**
3822     * Set the private content type of the text, which is the
3823     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3824     * field that will be filled in when creating an input connection.
3825     *
3826     * @see #getPrivateImeOptions()
3827     * @see EditorInfo#privateImeOptions
3828     * @attr ref android.R.styleable#TextView_privateImeOptions
3829     */
3830    public void setPrivateImeOptions(String type) {
3831        if (mInputContentType == null) mInputContentType = new InputContentType();
3832        mInputContentType.privateImeOptions = type;
3833    }
3834
3835    /**
3836     * Get the private type of the content.
3837     *
3838     * @see #setPrivateImeOptions(String)
3839     * @see EditorInfo#privateImeOptions
3840     */
3841    public String getPrivateImeOptions() {
3842        return mInputContentType != null
3843                ? mInputContentType.privateImeOptions : null;
3844    }
3845
3846    /**
3847     * Set the extra input data of the text, which is the
3848     * {@link EditorInfo#extras TextBoxAttribute.extras}
3849     * Bundle that will be filled in when creating an input connection.  The
3850     * given integer is the resource ID of an XML resource holding an
3851     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3852     *
3853     * @see #getInputExtras(boolean)
3854     * @see EditorInfo#extras
3855     * @attr ref android.R.styleable#TextView_editorExtras
3856     */
3857    public void setInputExtras(int xmlResId)
3858            throws XmlPullParserException, IOException {
3859        XmlResourceParser parser = getResources().getXml(xmlResId);
3860        if (mInputContentType == null) mInputContentType = new InputContentType();
3861        mInputContentType.extras = new Bundle();
3862        getResources().parseBundleExtras(parser, mInputContentType.extras);
3863    }
3864
3865    /**
3866     * Retrieve the input extras currently associated with the text view, which
3867     * can be viewed as well as modified.
3868     *
3869     * @param create If true, the extras will be created if they don't already
3870     * exist.  Otherwise, null will be returned if none have been created.
3871     * @see #setInputExtras(int)
3872     * @see EditorInfo#extras
3873     * @attr ref android.R.styleable#TextView_editorExtras
3874     */
3875    public Bundle getInputExtras(boolean create) {
3876        if (mInputContentType == null) {
3877            if (!create) return null;
3878            mInputContentType = new InputContentType();
3879        }
3880        if (mInputContentType.extras == null) {
3881            if (!create) return null;
3882            mInputContentType.extras = new Bundle();
3883        }
3884        return mInputContentType.extras;
3885    }
3886
3887    /**
3888     * Returns the error message that was set to be displayed with
3889     * {@link #setError}, or <code>null</code> if no error was set
3890     * or if it the error was cleared by the widget after user input.
3891     */
3892    public CharSequence getError() {
3893        return mError;
3894    }
3895
3896    /**
3897     * Sets the right-hand compound drawable of the TextView to the "error"
3898     * icon and sets an error message that will be displayed in a popup when
3899     * the TextView has focus.  The icon and error message will be reset to
3900     * null when any key events cause changes to the TextView's text.  If the
3901     * <code>error</code> is <code>null</code>, the error message and icon
3902     * will be cleared.
3903     */
3904    @android.view.RemotableViewMethod
3905    public void setError(CharSequence error) {
3906        if (error == null) {
3907            setError(null, null);
3908        } else {
3909            Drawable dr = getContext().getResources().
3910                getDrawable(com.android.internal.R.drawable.indicator_input_error);
3911
3912            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3913            setError(error, dr);
3914        }
3915    }
3916
3917    /**
3918     * Sets the right-hand compound drawable of the TextView to the specified
3919     * icon and sets an error message that will be displayed in a popup when
3920     * the TextView has focus.  The icon and error message will be reset to
3921     * null when any key events cause changes to the TextView's text.  The
3922     * drawable must already have had {@link Drawable#setBounds} set on it.
3923     * If the <code>error</code> is <code>null</code>, the error message will
3924     * be cleared (and you should provide a <code>null</code> icon as well).
3925     */
3926    public void setError(CharSequence error, Drawable icon) {
3927        error = TextUtils.stringOrSpannedString(error);
3928
3929        mError = error;
3930        mErrorWasChanged = true;
3931        final Drawables dr = mDrawables;
3932        if (dr != null) {
3933            switch (getResolvedLayoutDirection()) {
3934                default:
3935                case LAYOUT_DIRECTION_LTR:
3936                    setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon,
3937                            dr.mDrawableBottom);
3938                    break;
3939                case LAYOUT_DIRECTION_RTL:
3940                    setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight,
3941                            dr.mDrawableBottom);
3942                    break;
3943            }
3944        } else {
3945            setCompoundDrawables(null, null, icon, null);
3946        }
3947
3948        if (error == null) {
3949            if (mPopup != null) {
3950                if (mPopup.isShowing()) {
3951                    mPopup.dismiss();
3952                }
3953
3954                mPopup = null;
3955            }
3956        } else {
3957            if (isFocused()) {
3958                showError();
3959            }
3960        }
3961    }
3962
3963    private void showError() {
3964        if (getWindowToken() == null) {
3965            mShowErrorAfterAttach = true;
3966            return;
3967        }
3968
3969        if (mPopup == null) {
3970            LayoutInflater inflater = LayoutInflater.from(getContext());
3971            final TextView err = (TextView) inflater.inflate(
3972                    com.android.internal.R.layout.textview_hint, null);
3973
3974            final float scale = getResources().getDisplayMetrics().density;
3975            mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
3976            mPopup.setFocusable(false);
3977            // The user is entering text, so the input method is needed.  We
3978            // don't want the popup to be displayed on top of it.
3979            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
3980        }
3981
3982        TextView tv = (TextView) mPopup.getContentView();
3983        chooseSize(mPopup, mError, tv);
3984        tv.setText(mError);
3985
3986        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
3987        mPopup.fixDirection(mPopup.isAboveAnchor());
3988    }
3989
3990    private static class ErrorPopup extends PopupWindow {
3991        private boolean mAbove = false;
3992        private final TextView mView;
3993        private int mPopupInlineErrorBackgroundId = 0;
3994        private int mPopupInlineErrorAboveBackgroundId = 0;
3995
3996        ErrorPopup(TextView v, int width, int height) {
3997            super(v, width, height);
3998            mView = v;
3999            // Make sure the TextView has a background set as it will be used the first time it is
4000            // shown and positionned. Initialized with below background, which should have
4001            // dimensions identical to the above version for this to work (and is more likely).
4002            mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
4003                    com.android.internal.R.styleable.Theme_errorMessageBackground);
4004            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
4005        }
4006
4007        void fixDirection(boolean above) {
4008            mAbove = above;
4009
4010            if (above) {
4011                mPopupInlineErrorAboveBackgroundId =
4012                    getResourceId(mPopupInlineErrorAboveBackgroundId,
4013                            com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
4014            } else {
4015                mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
4016                        com.android.internal.R.styleable.Theme_errorMessageBackground);
4017            }
4018
4019            mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
4020                mPopupInlineErrorBackgroundId);
4021        }
4022
4023        private int getResourceId(int currentId, int index) {
4024            if (currentId == 0) {
4025                TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
4026                        R.styleable.Theme);
4027                currentId = styledAttributes.getResourceId(index, 0);
4028                styledAttributes.recycle();
4029            }
4030            return currentId;
4031        }
4032
4033        @Override
4034        public void update(int x, int y, int w, int h, boolean force) {
4035            super.update(x, y, w, h, force);
4036
4037            boolean above = isAboveAnchor();
4038            if (above != mAbove) {
4039                fixDirection(above);
4040            }
4041        }
4042    }
4043
4044    /**
4045     * Returns the Y offset to make the pointy top of the error point
4046     * at the middle of the error icon.
4047     */
4048    private int getErrorX() {
4049        /*
4050         * The "25" is the distance between the point and the right edge
4051         * of the background
4052         */
4053        final float scale = getResources().getDisplayMetrics().density;
4054
4055        final Drawables dr = mDrawables;
4056        return getWidth() - mPopup.getWidth() - getPaddingRight() -
4057                (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
4058    }
4059
4060    /**
4061     * Returns the Y offset to make the pointy top of the error point
4062     * at the bottom of the error icon.
4063     */
4064    private int getErrorY() {
4065        /*
4066         * Compound, not extended, because the icon is not clipped
4067         * if the text height is smaller.
4068         */
4069        final int compoundPaddingTop = getCompoundPaddingTop();
4070        int vspace = mBottom - mTop - getCompoundPaddingBottom() - compoundPaddingTop;
4071
4072        final Drawables dr = mDrawables;
4073        int icontop = compoundPaddingTop +
4074                (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
4075
4076        /*
4077         * The "2" is the distance between the point and the top edge
4078         * of the background.
4079         */
4080        final float scale = getResources().getDisplayMetrics().density;
4081        return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - getHeight() -
4082                (int) (2 * scale + 0.5f);
4083    }
4084
4085    private void hideError() {
4086        if (mPopup != null) {
4087            if (mPopup.isShowing()) {
4088                mPopup.dismiss();
4089            }
4090        }
4091
4092        mShowErrorAfterAttach = false;
4093    }
4094
4095    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
4096        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
4097        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
4098
4099        int defaultWidthInPixels = getResources().getDimensionPixelSize(
4100                com.android.internal.R.dimen.textview_error_popup_default_width);
4101        Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels,
4102                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
4103        float max = 0;
4104        for (int i = 0; i < l.getLineCount(); i++) {
4105            max = Math.max(max, l.getLineWidth(i));
4106        }
4107
4108        /*
4109         * Now set the popup size to be big enough for the text plus the border capped
4110         * to DEFAULT_MAX_POPUP_WIDTH
4111         */
4112        pop.setWidth(wid + (int) Math.ceil(max));
4113        pop.setHeight(ht + l.getHeight());
4114    }
4115
4116
4117    @Override
4118    protected boolean setFrame(int l, int t, int r, int b) {
4119        boolean result = super.setFrame(l, t, r, b);
4120
4121        if (mPopup != null) {
4122            TextView tv = (TextView) mPopup.getContentView();
4123            chooseSize(mPopup, mError, tv);
4124            mPopup.update(this, getErrorX(), getErrorY(),
4125                          mPopup.getWidth(), mPopup.getHeight());
4126        }
4127
4128        restartMarqueeIfNeeded();
4129
4130        return result;
4131    }
4132
4133    private void restartMarqueeIfNeeded() {
4134        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4135            mRestartMarquee = false;
4136            startMarquee();
4137        }
4138    }
4139
4140    /**
4141     * Sets the list of input filters that will be used if the buffer is
4142     * Editable.  Has no effect otherwise.
4143     *
4144     * @attr ref android.R.styleable#TextView_maxLength
4145     */
4146    public void setFilters(InputFilter[] filters) {
4147        if (filters == null) {
4148            throw new IllegalArgumentException();
4149        }
4150
4151        mFilters = filters;
4152
4153        if (mText instanceof Editable) {
4154            setFilters((Editable) mText, filters);
4155        }
4156    }
4157
4158    /**
4159     * Sets the list of input filters on the specified Editable,
4160     * and includes mInput in the list if it is an InputFilter.
4161     */
4162    private void setFilters(Editable e, InputFilter[] filters) {
4163        if (mInput instanceof InputFilter) {
4164            InputFilter[] nf = new InputFilter[filters.length + 1];
4165
4166            System.arraycopy(filters, 0, nf, 0, filters.length);
4167            nf[filters.length] = (InputFilter) mInput;
4168
4169            e.setFilters(nf);
4170        } else {
4171            e.setFilters(filters);
4172        }
4173    }
4174
4175    /**
4176     * Returns the current list of input filters.
4177     */
4178    public InputFilter[] getFilters() {
4179        return mFilters;
4180    }
4181
4182    /////////////////////////////////////////////////////////////////////////
4183
4184    private int getVerticalOffset(boolean forceNormal) {
4185        int voffset = 0;
4186        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4187
4188        Layout l = mLayout;
4189        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4190            l = mHintLayout;
4191        }
4192
4193        if (gravity != Gravity.TOP) {
4194            int boxht;
4195
4196            if (l == mHintLayout) {
4197                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
4198                        getCompoundPaddingBottom();
4199            } else {
4200                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
4201                        getExtendedPaddingBottom();
4202            }
4203            int textht = l.getHeight();
4204
4205            if (textht < boxht) {
4206                if (gravity == Gravity.BOTTOM)
4207                    voffset = boxht - textht;
4208                else // (gravity == Gravity.CENTER_VERTICAL)
4209                    voffset = (boxht - textht) >> 1;
4210            }
4211        }
4212        return voffset;
4213    }
4214
4215    private int getBottomVerticalOffset(boolean forceNormal) {
4216        int voffset = 0;
4217        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4218
4219        Layout l = mLayout;
4220        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4221            l = mHintLayout;
4222        }
4223
4224        if (gravity != Gravity.BOTTOM) {
4225            int boxht;
4226
4227            if (l == mHintLayout) {
4228                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
4229                        getCompoundPaddingBottom();
4230            } else {
4231                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
4232                        getExtendedPaddingBottom();
4233            }
4234            int textht = l.getHeight();
4235
4236            if (textht < boxht) {
4237                if (gravity == Gravity.TOP)
4238                    voffset = boxht - textht;
4239                else // (gravity == Gravity.CENTER_VERTICAL)
4240                    voffset = (boxht - textht) >> 1;
4241            }
4242        }
4243        return voffset;
4244    }
4245
4246    private void invalidateCursorPath() {
4247        if (mHighlightPathBogus) {
4248            invalidateCursor();
4249        } else {
4250            final int horizontalPadding = getCompoundPaddingLeft();
4251            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4252
4253            if (mCursorCount == 0) {
4254                synchronized (sTempRect) {
4255                    /*
4256                     * The reason for this concern about the thickness of the
4257                     * cursor and doing the floor/ceil on the coordinates is that
4258                     * some EditTexts (notably textfields in the Browser) have
4259                     * anti-aliased text where not all the characters are
4260                     * necessarily at integer-multiple locations.  This should
4261                     * make sure the entire cursor gets invalidated instead of
4262                     * sometimes missing half a pixel.
4263                     */
4264                    float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4265                    if (thick < 1.0f) {
4266                        thick = 1.0f;
4267                    }
4268
4269                    thick /= 2.0f;
4270
4271                    mHighlightPath.computeBounds(sTempRect, false);
4272
4273                    invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
4274                            (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),
4275                            (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
4276                            (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
4277                }
4278            } else {
4279                for (int i = 0; i < mCursorCount; i++) {
4280                    Rect bounds = mCursorDrawable[i].getBounds();
4281                    invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4282                            bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4283                }
4284            }
4285        }
4286    }
4287
4288    private void invalidateCursor() {
4289        int where = getSelectionEnd();
4290
4291        invalidateCursor(where, where, where);
4292    }
4293
4294    private void invalidateCursor(int a, int b, int c) {
4295        if (a >= 0 || b >= 0 || c >= 0) {
4296            int start = Math.min(Math.min(a, b), c);
4297            int end = Math.max(Math.max(a, b), c);
4298            invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
4299        }
4300    }
4301
4302    /**
4303     * Invalidates the region of text enclosed between the start and end text offsets.
4304     *
4305     * @hide
4306     */
4307    void invalidateRegion(int start, int end, boolean invalidateCursor) {
4308        if (mLayout == null) {
4309            invalidate();
4310        } else {
4311                int lineStart = mLayout.getLineForOffset(start);
4312                int top = mLayout.getLineTop(lineStart);
4313
4314                // This is ridiculous, but the descent from the line above
4315                // can hang down into the line we really want to redraw,
4316                // so we have to invalidate part of the line above to make
4317                // sure everything that needs to be redrawn really is.
4318                // (But not the whole line above, because that would cause
4319                // the same problem with the descenders on the line above it!)
4320                if (lineStart > 0) {
4321                    top -= mLayout.getLineDescent(lineStart - 1);
4322                }
4323
4324                int lineEnd;
4325
4326                if (start == end)
4327                    lineEnd = lineStart;
4328                else
4329                    lineEnd = mLayout.getLineForOffset(end);
4330
4331                int bottom = mLayout.getLineBottom(lineEnd);
4332
4333                if (invalidateCursor) {
4334                    for (int i = 0; i < mCursorCount; i++) {
4335                        Rect bounds = mCursorDrawable[i].getBounds();
4336                        top = Math.min(top, bounds.top);
4337                        bottom = Math.max(bottom, bounds.bottom);
4338                    }
4339                }
4340
4341                final int compoundPaddingLeft = getCompoundPaddingLeft();
4342                final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4343
4344                int left, right;
4345                if (lineStart == lineEnd && !invalidateCursor) {
4346                    left = (int) mLayout.getPrimaryHorizontal(start);
4347                    right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4348                    left += compoundPaddingLeft;
4349                    right += compoundPaddingLeft;
4350                } else {
4351                    // Rectangle bounding box when the region spans several lines
4352                    left = compoundPaddingLeft;
4353                    right = getWidth() - getCompoundPaddingRight();
4354                }
4355
4356                invalidate(mScrollX + left, verticalPadding + top,
4357                        mScrollX + right, verticalPadding + bottom);
4358        }
4359    }
4360
4361    private void registerForPreDraw() {
4362        if (!mPreDrawRegistered) {
4363            getViewTreeObserver().addOnPreDrawListener(this);
4364            mPreDrawRegistered = true;
4365        }
4366    }
4367
4368    /**
4369     * {@inheritDoc}
4370     */
4371    public boolean onPreDraw() {
4372        if (mLayout == null) {
4373            assumeLayout();
4374        }
4375
4376        boolean changed = false;
4377
4378        if (mMovement != null) {
4379            /* This code also provides auto-scrolling when a cursor is moved using a
4380             * CursorController (insertion point or selection limits).
4381             * For selection, ensure start or end is visible depending on controller's state.
4382             */
4383            int curs = getSelectionEnd();
4384            // Do not create the controller if it is not already created.
4385            if (mSelectionModifierCursorController != null &&
4386                    mSelectionModifierCursorController.isSelectionStartDragged()) {
4387                curs = getSelectionStart();
4388            }
4389
4390            /*
4391             * TODO: This should really only keep the end in view if
4392             * it already was before the text changed.  I'm not sure
4393             * of a good way to tell from here if it was.
4394             */
4395            if (curs < 0 &&
4396                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
4397                curs = mText.length();
4398            }
4399
4400            if (curs >= 0) {
4401                changed = bringPointIntoView(curs);
4402            }
4403        } else {
4404            changed = bringTextIntoView();
4405        }
4406
4407        // This has to be checked here since:
4408        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4409        //   a screen rotation) since layout is not yet initialized at that point.
4410        if (mCreatedWithASelection) {
4411            startSelectionActionMode();
4412            mCreatedWithASelection = false;
4413        }
4414
4415        // Phone specific code (there is no ExtractEditText on tablets).
4416        // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4417        // not be set. Do the test here instead.
4418        if (this instanceof ExtractEditText && hasSelection()) {
4419            startSelectionActionMode();
4420        }
4421
4422        getViewTreeObserver().removeOnPreDrawListener(this);
4423        mPreDrawRegistered = false;
4424
4425        return !changed;
4426    }
4427
4428    @Override
4429    protected void onAttachedToWindow() {
4430        super.onAttachedToWindow();
4431
4432        mTemporaryDetach = false;
4433
4434        if (mShowErrorAfterAttach) {
4435            showError();
4436            mShowErrorAfterAttach = false;
4437        }
4438
4439        final ViewTreeObserver observer = getViewTreeObserver();
4440        // No need to create the controller.
4441        // The get method will add the listener on controller creation.
4442        if (mInsertionPointCursorController != null) {
4443            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
4444        }
4445        if (mSelectionModifierCursorController != null) {
4446            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
4447        }
4448
4449        // Resolve drawables as the layout direction has been resolved
4450        resolveDrawables();
4451
4452        updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
4453    }
4454
4455    @Override
4456    protected void onDetachedFromWindow() {
4457        super.onDetachedFromWindow();
4458
4459        if (mPreDrawRegistered) {
4460            getViewTreeObserver().removeOnPreDrawListener(this);
4461            mPreDrawRegistered = false;
4462        }
4463
4464        if (mError != null) {
4465            hideError();
4466        }
4467
4468        if (mBlink != null) {
4469            mBlink.removeCallbacks(mBlink);
4470        }
4471
4472        if (mInsertionPointCursorController != null) {
4473            mInsertionPointCursorController.onDetached();
4474        }
4475
4476        if (mSelectionModifierCursorController != null) {
4477            mSelectionModifierCursorController.onDetached();
4478        }
4479
4480        if (mShowSuggestionRunnable != null) {
4481            removeCallbacks(mShowSuggestionRunnable);
4482        }
4483
4484        hideControllers();
4485
4486        resetResolvedDrawables();
4487
4488        if (mTextDisplayList != null) {
4489            mTextDisplayList.invalidate();
4490        }
4491
4492        if (mSpellChecker != null) {
4493            mSpellChecker.closeSession();
4494            // Forces the creation of a new SpellChecker next time this window is created.
4495            // Will handle the cases where the settings has been changed in the meantime.
4496            mSpellChecker = null;
4497        }
4498    }
4499
4500    @Override
4501    protected boolean isPaddingOffsetRequired() {
4502        return mShadowRadius != 0 || mDrawables != null;
4503    }
4504
4505    @Override
4506    protected int getLeftPaddingOffset() {
4507        return getCompoundPaddingLeft() - mPaddingLeft +
4508                (int) Math.min(0, mShadowDx - mShadowRadius);
4509    }
4510
4511    @Override
4512    protected int getTopPaddingOffset() {
4513        return (int) Math.min(0, mShadowDy - mShadowRadius);
4514    }
4515
4516    @Override
4517    protected int getBottomPaddingOffset() {
4518        return (int) Math.max(0, mShadowDy + mShadowRadius);
4519    }
4520
4521    @Override
4522    protected int getRightPaddingOffset() {
4523        return -(getCompoundPaddingRight() - mPaddingRight) +
4524                (int) Math.max(0, mShadowDx + mShadowRadius);
4525    }
4526
4527    @Override
4528    protected boolean verifyDrawable(Drawable who) {
4529        final boolean verified = super.verifyDrawable(who);
4530        if (!verified && mDrawables != null) {
4531            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
4532                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4533                    who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
4534        }
4535        return verified;
4536    }
4537
4538    @Override
4539    public void jumpDrawablesToCurrentState() {
4540        super.jumpDrawablesToCurrentState();
4541        if (mDrawables != null) {
4542            if (mDrawables.mDrawableLeft != null) {
4543                mDrawables.mDrawableLeft.jumpToCurrentState();
4544            }
4545            if (mDrawables.mDrawableTop != null) {
4546                mDrawables.mDrawableTop.jumpToCurrentState();
4547            }
4548            if (mDrawables.mDrawableRight != null) {
4549                mDrawables.mDrawableRight.jumpToCurrentState();
4550            }
4551            if (mDrawables.mDrawableBottom != null) {
4552                mDrawables.mDrawableBottom.jumpToCurrentState();
4553            }
4554            if (mDrawables.mDrawableStart != null) {
4555                mDrawables.mDrawableStart.jumpToCurrentState();
4556            }
4557            if (mDrawables.mDrawableEnd != null) {
4558                mDrawables.mDrawableEnd.jumpToCurrentState();
4559            }
4560        }
4561    }
4562
4563    @Override
4564    public void invalidateDrawable(Drawable drawable) {
4565        if (verifyDrawable(drawable)) {
4566            final Rect dirty = drawable.getBounds();
4567            int scrollX = mScrollX;
4568            int scrollY = mScrollY;
4569
4570            // IMPORTANT: The coordinates below are based on the coordinates computed
4571            // for each compound drawable in onDraw(). Make sure to update each section
4572            // accordingly.
4573            final TextView.Drawables drawables = mDrawables;
4574            if (drawables != null) {
4575                if (drawable == drawables.mDrawableLeft) {
4576                    final int compoundPaddingTop = getCompoundPaddingTop();
4577                    final int compoundPaddingBottom = getCompoundPaddingBottom();
4578                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4579
4580                    scrollX += mPaddingLeft;
4581                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4582                } else if (drawable == drawables.mDrawableRight) {
4583                    final int compoundPaddingTop = getCompoundPaddingTop();
4584                    final int compoundPaddingBottom = getCompoundPaddingBottom();
4585                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4586
4587                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4588                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4589                } else if (drawable == drawables.mDrawableTop) {
4590                    final int compoundPaddingLeft = getCompoundPaddingLeft();
4591                    final int compoundPaddingRight = getCompoundPaddingRight();
4592                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
4593
4594                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4595                    scrollY += mPaddingTop;
4596                } else if (drawable == drawables.mDrawableBottom) {
4597                    final int compoundPaddingLeft = getCompoundPaddingLeft();
4598                    final int compoundPaddingRight = getCompoundPaddingRight();
4599                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
4600
4601                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4602                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4603                }
4604            }
4605
4606            invalidate(dirty.left + scrollX, dirty.top + scrollY,
4607                    dirty.right + scrollX, dirty.bottom + scrollY);
4608        }
4609    }
4610
4611    /**
4612     * @hide
4613     */
4614    @Override
4615    public int getResolvedLayoutDirection(Drawable who) {
4616        if (who == null) return View.LAYOUT_DIRECTION_LTR;
4617        if (mDrawables != null) {
4618            final Drawables drawables = mDrawables;
4619            if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight ||
4620                who == drawables.mDrawableTop || who == drawables.mDrawableBottom ||
4621                who == drawables.mDrawableStart || who == drawables.mDrawableEnd) {
4622                return getResolvedLayoutDirection();
4623            }
4624        }
4625        return super.getResolvedLayoutDirection(who);
4626    }
4627
4628    @Override
4629    protected boolean onSetAlpha(int alpha) {
4630        // Alpha is supported if and only if the drawing can be done in one pass.
4631        // TODO text with spans with a background color currently do not respect this alpha.
4632        if (getBackground() == null) {
4633            mCurrentAlpha = alpha;
4634            final Drawables dr = mDrawables;
4635            if (dr != null) {
4636                if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
4637                if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
4638                if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
4639                if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
4640                if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha);
4641                if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha);
4642            }
4643            return true;
4644        }
4645
4646        mCurrentAlpha = 255;
4647        return false;
4648    }
4649
4650    /**
4651     * When a TextView is used to display a useful piece of information to the user (such as a
4652     * contact's address), it should be made selectable, so that the user can select and copy this
4653     * content.
4654     *
4655     * Use {@link #setTextIsSelectable(boolean)} or the
4656     * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
4657     * selectable (text is not selectable by default).
4658     *
4659     * Note that this method simply returns the state of this flag. Although this flag has to be set
4660     * in order to select text in non-editable TextView, the content of an {@link EditText} can
4661     * always be selected, independently of the value of this flag.
4662     *
4663     * @return True if the text displayed in this TextView can be selected by the user.
4664     *
4665     * @attr ref android.R.styleable#TextView_textIsSelectable
4666     */
4667    public boolean isTextSelectable() {
4668        return mTextIsSelectable;
4669    }
4670
4671    /**
4672     * Sets whether or not (default) the content of this view is selectable by the user.
4673     *
4674     * Note that this methods affect the {@link #setFocusable(boolean)},
4675     * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
4676     * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
4677     * customized.
4678     *
4679     * See {@link #isTextSelectable} for details.
4680     *
4681     * @param selectable Whether or not the content of this TextView should be selectable.
4682     */
4683    public void setTextIsSelectable(boolean selectable) {
4684        if (mTextIsSelectable == selectable) return;
4685
4686        mTextIsSelectable = selectable;
4687
4688        setFocusableInTouchMode(selectable);
4689        setFocusable(selectable);
4690        setClickable(selectable);
4691        setLongClickable(selectable);
4692
4693        // mInputType is already EditorInfo.TYPE_NULL and mInput is null;
4694
4695        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
4696        setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
4697
4698        // Called by setText above, but safer in case of future code changes
4699        prepareCursorControllers();
4700    }
4701
4702    @Override
4703    protected int[] onCreateDrawableState(int extraSpace) {
4704        final int[] drawableState;
4705
4706        if (mSingleLine) {
4707            drawableState = super.onCreateDrawableState(extraSpace);
4708        } else {
4709            drawableState = super.onCreateDrawableState(extraSpace + 1);
4710            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4711        }
4712
4713        if (mTextIsSelectable) {
4714            // Disable pressed state, which was introduced when TextView was made clickable.
4715            // Prevents text color change.
4716            // setClickable(false) would have a similar effect, but it also disables focus changes
4717            // and long press actions, which are both needed by text selection.
4718            final int length = drawableState.length;
4719            for (int i = 0; i < length; i++) {
4720                if (drawableState[i] == R.attr.state_pressed) {
4721                    final int[] nonPressedState = new int[length - 1];
4722                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4723                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4724                    return nonPressedState;
4725                }
4726            }
4727        }
4728
4729        return drawableState;
4730    }
4731
4732    @Override
4733    protected void onDraw(Canvas canvas) {
4734        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return;
4735
4736        restartMarqueeIfNeeded();
4737
4738        // Draw the background for this view
4739        super.onDraw(canvas);
4740
4741        final int compoundPaddingLeft = getCompoundPaddingLeft();
4742        final int compoundPaddingTop = getCompoundPaddingTop();
4743        final int compoundPaddingRight = getCompoundPaddingRight();
4744        final int compoundPaddingBottom = getCompoundPaddingBottom();
4745        final int scrollX = mScrollX;
4746        final int scrollY = mScrollY;
4747        final int right = mRight;
4748        final int left = mLeft;
4749        final int bottom = mBottom;
4750        final int top = mTop;
4751
4752        final Drawables dr = mDrawables;
4753        if (dr != null) {
4754            /*
4755             * Compound, not extended, because the icon is not clipped
4756             * if the text height is smaller.
4757             */
4758
4759            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4760            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4761
4762            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4763            // Make sure to update invalidateDrawable() when changing this code.
4764            if (dr.mDrawableLeft != null) {
4765                canvas.save();
4766                canvas.translate(scrollX + mPaddingLeft,
4767                                 scrollY + compoundPaddingTop +
4768                                 (vspace - dr.mDrawableHeightLeft) / 2);
4769                dr.mDrawableLeft.draw(canvas);
4770                canvas.restore();
4771            }
4772
4773            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4774            // Make sure to update invalidateDrawable() when changing this code.
4775            if (dr.mDrawableRight != null) {
4776                canvas.save();
4777                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
4778                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4779                dr.mDrawableRight.draw(canvas);
4780                canvas.restore();
4781            }
4782
4783            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4784            // Make sure to update invalidateDrawable() when changing this code.
4785            if (dr.mDrawableTop != null) {
4786                canvas.save();
4787                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
4788                        scrollY + mPaddingTop);
4789                dr.mDrawableTop.draw(canvas);
4790                canvas.restore();
4791            }
4792
4793            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4794            // Make sure to update invalidateDrawable() when changing this code.
4795            if (dr.mDrawableBottom != null) {
4796                canvas.save();
4797                canvas.translate(scrollX + compoundPaddingLeft +
4798                        (hspace - dr.mDrawableWidthBottom) / 2,
4799                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
4800                dr.mDrawableBottom.draw(canvas);
4801                canvas.restore();
4802            }
4803        }
4804
4805        int color = mCurTextColor;
4806
4807        if (mLayout == null) {
4808            assumeLayout();
4809        }
4810
4811        Layout layout = mLayout;
4812        int cursorcolor = color;
4813
4814        if (mHint != null && mText.length() == 0) {
4815            if (mHintTextColor != null) {
4816                color = mCurHintTextColor;
4817            }
4818
4819            layout = mHintLayout;
4820        }
4821
4822        mTextPaint.setColor(color);
4823        if (mCurrentAlpha != 255) {
4824            // If set, the alpha will override the color's alpha. Multiply the alphas.
4825            mTextPaint.setAlpha((mCurrentAlpha * Color.alpha(color)) / 255);
4826        }
4827        mTextPaint.drawableState = getDrawableState();
4828
4829        canvas.save();
4830        /*  Would be faster if we didn't have to do this. Can we chop the
4831            (displayable) text so that we don't need to do this ever?
4832        */
4833
4834        int extendedPaddingTop = getExtendedPaddingTop();
4835        int extendedPaddingBottom = getExtendedPaddingBottom();
4836
4837        float clipLeft = compoundPaddingLeft + scrollX;
4838        float clipTop = extendedPaddingTop + scrollY;
4839        float clipRight = right - left - compoundPaddingRight + scrollX;
4840        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
4841
4842        if (mShadowRadius != 0) {
4843            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
4844            clipRight += Math.max(0, mShadowDx + mShadowRadius);
4845
4846            clipTop += Math.min(0, mShadowDy - mShadowRadius);
4847            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
4848        }
4849
4850        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4851
4852        int voffsetText = 0;
4853        int voffsetCursor = 0;
4854
4855        // translate in by our padding
4856        {
4857            /* shortcircuit calling getVerticaOffset() */
4858            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4859                voffsetText = getVerticalOffset(false);
4860                voffsetCursor = getVerticalOffset(true);
4861            }
4862            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
4863        }
4864
4865        final int layoutDirection = getResolvedLayoutDirection();
4866        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
4867        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
4868                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
4869            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
4870                    (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
4871                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
4872                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
4873            }
4874
4875            if (mMarquee != null && mMarquee.isRunning()) {
4876                canvas.translate(-mMarquee.mScroll, 0.0f);
4877            }
4878        }
4879
4880        Path highlight = null;
4881        int selStart = -1, selEnd = -1;
4882        boolean drawCursor = false;
4883
4884        //  If there is no movement method, then there can be no selection.
4885        //  Check that first and attempt to skip everything having to do with
4886        //  the cursor.
4887        //  XXX This is not strictly true -- a program could set the
4888        //  selection manually if it really wanted to.
4889        if (mMovement != null && (isFocused() || isPressed())) {
4890            selStart = getSelectionStart();
4891            selEnd = getSelectionEnd();
4892
4893            if (selStart >= 0) {
4894                if (mHighlightPath == null) mHighlightPath = new Path();
4895
4896                if (selStart == selEnd) {
4897                    if (isCursorVisible() &&
4898                            (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
4899                        if (mHighlightPathBogus) {
4900                            mHighlightPath.reset();
4901                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
4902                            updateCursorsPositions();
4903                            mHighlightPathBogus = false;
4904                        }
4905
4906                        // XXX should pass to skin instead of drawing directly
4907                        mHighlightPaint.setColor(cursorcolor);
4908                        if (mCurrentAlpha != 255) {
4909                            mHighlightPaint.setAlpha(
4910                                    (mCurrentAlpha * Color.alpha(cursorcolor)) / 255);
4911                        }
4912                        mHighlightPaint.setStyle(Paint.Style.STROKE);
4913                        highlight = mHighlightPath;
4914                        drawCursor = mCursorCount > 0;
4915                    }
4916                } else if (textCanBeSelected()) {
4917                    if (mHighlightPathBogus) {
4918                        mHighlightPath.reset();
4919                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4920                        mHighlightPathBogus = false;
4921                    }
4922
4923                    // XXX should pass to skin instead of drawing directly
4924                    mHighlightPaint.setColor(mHighlightColor);
4925                    if (mCurrentAlpha != 255) {
4926                        mHighlightPaint.setAlpha(
4927                                (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
4928                    }
4929                    mHighlightPaint.setStyle(Paint.Style.FILL);
4930
4931                    highlight = mHighlightPath;
4932                }
4933            }
4934        }
4935
4936        final InputMethodState ims = mInputMethodState;
4937        final int cursorOffsetVertical = voffsetCursor - voffsetText;
4938        if (ims != null && ims.mBatchEditNesting == 0) {
4939            InputMethodManager imm = InputMethodManager.peekInstance();
4940            if (imm != null) {
4941                if (imm.isActive(this)) {
4942                    boolean reported = false;
4943                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
4944                        // We are in extract mode and the content has changed
4945                        // in some way... just report complete new text to the
4946                        // input method.
4947                        reported = reportExtractedText();
4948                    }
4949                    if (!reported && highlight != null) {
4950                        int candStart = -1;
4951                        int candEnd = -1;
4952                        if (mText instanceof Spannable) {
4953                            Spannable sp = (Spannable)mText;
4954                            candStart = EditableInputConnection.getComposingSpanStart(sp);
4955                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4956                        }
4957                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4958                    }
4959                }
4960
4961                if (imm.isWatchingCursor(this) && highlight != null) {
4962                    highlight.computeBounds(ims.mTmpRectF, true);
4963                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4964
4965                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
4966                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4967
4968                    ims.mTmpRectF.offset(0, cursorOffsetVertical);
4969
4970                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4971                            (int)(ims.mTmpRectF.top + 0.5),
4972                            (int)(ims.mTmpRectF.right + 0.5),
4973                            (int)(ims.mTmpRectF.bottom + 0.5));
4974
4975                    imm.updateCursor(this,
4976                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4977                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4978                }
4979            }
4980        }
4981
4982        if (mCorrectionHighlighter != null) {
4983            mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
4984        }
4985
4986        if (drawCursor) {
4987            drawCursor(canvas, cursorOffsetVertical);
4988            // Rely on the drawable entirely, do not draw the cursor line.
4989            // Has to be done after the IMM related code above which relies on the highlight.
4990            highlight = null;
4991        }
4992
4993        if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
4994            final int width = mRight - mLeft;
4995            final int height = mBottom - mTop;
4996
4997            if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
4998                    !mTextDisplayListIsValid) {
4999                if (mTextDisplayList == null) {
5000                    mTextDisplayList = getHardwareRenderer().createDisplayList();
5001                }
5002
5003                final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
5004                try {
5005                    hardwareCanvas.setViewport(width, height);
5006                    // The dirty rect should always be null for a display list
5007                    hardwareCanvas.onPreDraw(null);
5008                    layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
5009                } finally {
5010                    hardwareCanvas.onPostDraw();
5011                    mTextDisplayList.end();
5012                    mTextDisplayListIsValid = true;
5013                }
5014            }
5015            ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList,
5016                    mScrollX + width, mScrollY + height, null);
5017        } else {
5018            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5019        }
5020
5021        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5022            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
5023            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5024        }
5025
5026        canvas.restore();
5027    }
5028
5029    private void updateCursorsPositions() {
5030        if (mCursorDrawableRes == 0) {
5031            mCursorCount = 0;
5032            return;
5033        }
5034
5035        final int offset = getSelectionStart();
5036        final int line = mLayout.getLineForOffset(offset);
5037        final int top = mLayout.getLineTop(line);
5038        final int bottom = mLayout.getLineTop(line + 1);
5039
5040        mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
5041
5042        int middle = bottom;
5043        if (mCursorCount == 2) {
5044            // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
5045            middle = (top + bottom) >> 1;
5046        }
5047
5048        updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
5049
5050        if (mCursorCount == 2) {
5051            updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
5052        }
5053    }
5054
5055    private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
5056        if (mCursorDrawable[cursorIndex] == null)
5057            mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
5058
5059        if (mTempRect == null) mTempRect = new Rect();
5060
5061        mCursorDrawable[cursorIndex].getPadding(mTempRect);
5062        final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
5063        horizontal = Math.max(0.5f, horizontal - 0.5f);
5064        final int left = (int) (horizontal) - mTempRect.left;
5065        mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
5066                bottom + mTempRect.bottom);
5067    }
5068
5069    private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
5070        final boolean translate = cursorOffsetVertical != 0;
5071        if (translate) canvas.translate(0, cursorOffsetVertical);
5072        for (int i = 0; i < mCursorCount; i++) {
5073            mCursorDrawable[i].draw(canvas);
5074        }
5075        if (translate) canvas.translate(0, -cursorOffsetVertical);
5076    }
5077
5078    @Override
5079    public void getFocusedRect(Rect r) {
5080        if (mLayout == null) {
5081            super.getFocusedRect(r);
5082            return;
5083        }
5084
5085        int selEnd = getSelectionEnd();
5086        if (selEnd < 0) {
5087            super.getFocusedRect(r);
5088            return;
5089        }
5090
5091        int selStart = getSelectionStart();
5092        if (selStart < 0 || selStart >= selEnd) {
5093            int line = mLayout.getLineForOffset(selEnd);
5094            r.top = mLayout.getLineTop(line);
5095            r.bottom = mLayout.getLineBottom(line);
5096            r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5097            r.right = r.left + 4;
5098        } else {
5099            int lineStart = mLayout.getLineForOffset(selStart);
5100            int lineEnd = mLayout.getLineForOffset(selEnd);
5101            r.top = mLayout.getLineTop(lineStart);
5102            r.bottom = mLayout.getLineBottom(lineEnd);
5103            if (lineStart == lineEnd) {
5104                r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5105                r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5106            } else {
5107                // Selection extends across multiple lines -- the focused
5108                // rect covers the entire width.
5109                if (mHighlightPath == null) mHighlightPath = new Path();
5110                if (mHighlightPathBogus) {
5111                    mHighlightPath.reset();
5112                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5113                    mHighlightPathBogus = false;
5114                }
5115                synchronized (sTempRect) {
5116                    mHighlightPath.computeBounds(sTempRect, true);
5117                    r.left = (int)sTempRect.left-1;
5118                    r.right = (int)sTempRect.right+1;
5119                }
5120            }
5121        }
5122
5123        // Adjust for padding and gravity.
5124        int paddingLeft = getCompoundPaddingLeft();
5125        int paddingTop = getExtendedPaddingTop();
5126        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5127            paddingTop += getVerticalOffset(false);
5128        }
5129        r.offset(paddingLeft, paddingTop);
5130    }
5131
5132    /**
5133     * Return the number of lines of text, or 0 if the internal Layout has not
5134     * been built.
5135     */
5136    public int getLineCount() {
5137        return mLayout != null ? mLayout.getLineCount() : 0;
5138    }
5139
5140    /**
5141     * Return the baseline for the specified line (0...getLineCount() - 1)
5142     * If bounds is not null, return the top, left, right, bottom extents
5143     * of the specified line in it. If the internal Layout has not been built,
5144     * return 0 and set bounds to (0, 0, 0, 0)
5145     * @param line which line to examine (0..getLineCount() - 1)
5146     * @param bounds Optional. If not null, it returns the extent of the line
5147     * @return the Y-coordinate of the baseline
5148     */
5149    public int getLineBounds(int line, Rect bounds) {
5150        if (mLayout == null) {
5151            if (bounds != null) {
5152                bounds.set(0, 0, 0, 0);
5153            }
5154            return 0;
5155        }
5156        else {
5157            int baseline = mLayout.getLineBounds(line, bounds);
5158
5159            int voffset = getExtendedPaddingTop();
5160            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5161                voffset += getVerticalOffset(true);
5162            }
5163            if (bounds != null) {
5164                bounds.offset(getCompoundPaddingLeft(), voffset);
5165            }
5166            return baseline + voffset;
5167        }
5168    }
5169
5170    @Override
5171    public int getBaseline() {
5172        if (mLayout == null) {
5173            return super.getBaseline();
5174        }
5175
5176        int voffset = 0;
5177        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5178            voffset = getVerticalOffset(true);
5179        }
5180
5181        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5182    }
5183
5184    /**
5185     * @hide
5186     * @param offsetRequired
5187     */
5188    @Override
5189    protected int getFadeTop(boolean offsetRequired) {
5190        if (mLayout == null) return 0;
5191
5192        int voffset = 0;
5193        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5194            voffset = getVerticalOffset(true);
5195        }
5196
5197        if (offsetRequired) voffset += getTopPaddingOffset();
5198
5199        return getExtendedPaddingTop() + voffset;
5200    }
5201
5202    /**
5203     * @hide
5204     * @param offsetRequired
5205     */
5206    @Override
5207    protected int getFadeHeight(boolean offsetRequired) {
5208        return mLayout != null ? mLayout.getHeight() : 0;
5209    }
5210
5211    @Override
5212    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5213        if (keyCode == KeyEvent.KEYCODE_BACK) {
5214            boolean isInSelectionMode = mSelectionActionMode != null;
5215
5216            if (isInSelectionMode) {
5217                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5218                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5219                    if (state != null) {
5220                        state.startTracking(event, this);
5221                    }
5222                    return true;
5223                } else if (event.getAction() == KeyEvent.ACTION_UP) {
5224                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5225                    if (state != null) {
5226                        state.handleUpEvent(event);
5227                    }
5228                    if (event.isTracking() && !event.isCanceled()) {
5229                        stopSelectionActionMode();
5230                        return true;
5231                    }
5232                }
5233            }
5234        }
5235        return super.onKeyPreIme(keyCode, event);
5236    }
5237
5238    @Override
5239    public boolean onKeyDown(int keyCode, KeyEvent event) {
5240        int which = doKeyDown(keyCode, event, null);
5241        if (which == 0) {
5242            // Go through default dispatching.
5243            return super.onKeyDown(keyCode, event);
5244        }
5245
5246        return true;
5247    }
5248
5249    @Override
5250    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5251        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5252
5253        int which = doKeyDown(keyCode, down, event);
5254        if (which == 0) {
5255            // Go through default dispatching.
5256            return super.onKeyMultiple(keyCode, repeatCount, event);
5257        }
5258        if (which == -1) {
5259            // Consumed the whole thing.
5260            return true;
5261        }
5262
5263        repeatCount--;
5264
5265        // We are going to dispatch the remaining events to either the input
5266        // or movement method.  To do this, we will just send a repeated stream
5267        // of down and up events until we have done the complete repeatCount.
5268        // It would be nice if those interfaces had an onKeyMultiple() method,
5269        // but adding that is a more complicated change.
5270        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5271        if (which == 1) {
5272            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
5273            while (--repeatCount > 0) {
5274                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
5275                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
5276            }
5277            hideErrorIfUnchanged();
5278
5279        } else if (which == 2) {
5280            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5281            while (--repeatCount > 0) {
5282                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5283                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5284            }
5285        }
5286
5287        return true;
5288    }
5289
5290    /**
5291     * Returns true if pressing ENTER in this field advances focus instead
5292     * of inserting the character.  This is true mostly in single-line fields,
5293     * but also in mail addresses and subjects which will display on multiple
5294     * lines but where it doesn't make sense to insert newlines.
5295     */
5296    private boolean shouldAdvanceFocusOnEnter() {
5297        if (mInput == null) {
5298            return false;
5299        }
5300
5301        if (mSingleLine) {
5302            return true;
5303        }
5304
5305        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5306            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
5307            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5308                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5309                return true;
5310            }
5311        }
5312
5313        return false;
5314    }
5315
5316    /**
5317     * Returns true if pressing TAB in this field advances focus instead
5318     * of inserting the character.  Insert tabs only in multi-line editors.
5319     */
5320    private boolean shouldAdvanceFocusOnTab() {
5321        if (mInput != null && !mSingleLine) {
5322            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5323                int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
5324                if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5325                        || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5326                    return false;
5327                }
5328            }
5329        }
5330        return true;
5331    }
5332
5333    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5334        if (!isEnabled()) {
5335            return 0;
5336        }
5337
5338        switch (keyCode) {
5339            case KeyEvent.KEYCODE_ENTER:
5340                if (event.hasNoModifiers()) {
5341                    // When mInputContentType is set, we know that we are
5342                    // running in a "modern" cupcake environment, so don't need
5343                    // to worry about the application trying to capture
5344                    // enter key events.
5345                    if (mInputContentType != null) {
5346                        // If there is an action listener, given them a
5347                        // chance to consume the event.
5348                        if (mInputContentType.onEditorActionListener != null &&
5349                                mInputContentType.onEditorActionListener.onEditorAction(
5350                                this, EditorInfo.IME_NULL, event)) {
5351                            mInputContentType.enterDown = true;
5352                            // We are consuming the enter key for them.
5353                            return -1;
5354                        }
5355                    }
5356
5357                    // If our editor should move focus when enter is pressed, or
5358                    // this is a generated event from an IME action button, then
5359                    // don't let it be inserted into the text.
5360                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5361                            || shouldAdvanceFocusOnEnter()) {
5362                        if (hasOnClickListeners()) {
5363                            return 0;
5364                        }
5365                        return -1;
5366                    }
5367                }
5368                break;
5369
5370            case KeyEvent.KEYCODE_DPAD_CENTER:
5371                if (event.hasNoModifiers()) {
5372                    if (shouldAdvanceFocusOnEnter()) {
5373                        return 0;
5374                    }
5375                }
5376                break;
5377
5378            case KeyEvent.KEYCODE_TAB:
5379                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5380                    if (shouldAdvanceFocusOnTab()) {
5381                        return 0;
5382                    }
5383                }
5384                break;
5385
5386                // Has to be done on key down (and not on key up) to correctly be intercepted.
5387            case KeyEvent.KEYCODE_BACK:
5388                if (mSelectionActionMode != null) {
5389                    stopSelectionActionMode();
5390                    return -1;
5391                }
5392                break;
5393        }
5394
5395        if (mInput != null) {
5396            resetErrorChangedFlag();
5397
5398            boolean doDown = true;
5399            if (otherEvent != null) {
5400                try {
5401                    beginBatchEdit();
5402                    final boolean handled = mInput.onKeyOther(this, (Editable) mText, otherEvent);
5403                    hideErrorIfUnchanged();
5404                    doDown = false;
5405                    if (handled) {
5406                        return -1;
5407                    }
5408                } catch (AbstractMethodError e) {
5409                    // onKeyOther was added after 1.0, so if it isn't
5410                    // implemented we need to try to dispatch as a regular down.
5411                } finally {
5412                    endBatchEdit();
5413                }
5414            }
5415
5416            if (doDown) {
5417                beginBatchEdit();
5418                final boolean handled = mInput.onKeyDown(this, (Editable) mText, keyCode, event);
5419                endBatchEdit();
5420                hideErrorIfUnchanged();
5421                if (handled) return 1;
5422            }
5423        }
5424
5425        // bug 650865: sometimes we get a key event before a layout.
5426        // don't try to move around if we don't know the layout.
5427
5428        if (mMovement != null && mLayout != null) {
5429            boolean doDown = true;
5430            if (otherEvent != null) {
5431                try {
5432                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5433                            otherEvent);
5434                    doDown = false;
5435                    if (handled) {
5436                        return -1;
5437                    }
5438                } catch (AbstractMethodError e) {
5439                    // onKeyOther was added after 1.0, so if it isn't
5440                    // implemented we need to try to dispatch as a regular down.
5441                }
5442            }
5443            if (doDown) {
5444                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
5445                    return 2;
5446            }
5447        }
5448
5449        return 0;
5450    }
5451
5452    /**
5453     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5454     * can be recorded.
5455     * @hide
5456     */
5457    public void resetErrorChangedFlag() {
5458        /*
5459         * Keep track of what the error was before doing the input
5460         * so that if an input filter changed the error, we leave
5461         * that error showing.  Otherwise, we take down whatever
5462         * error was showing when the user types something.
5463         */
5464        mErrorWasChanged = false;
5465    }
5466
5467    /**
5468     * @hide
5469     */
5470    public void hideErrorIfUnchanged() {
5471        if (mError != null && !mErrorWasChanged) {
5472            setError(null, null);
5473        }
5474    }
5475
5476    @Override
5477    public boolean onKeyUp(int keyCode, KeyEvent event) {
5478        if (!isEnabled()) {
5479            return super.onKeyUp(keyCode, event);
5480        }
5481
5482        switch (keyCode) {
5483            case KeyEvent.KEYCODE_DPAD_CENTER:
5484                if (event.hasNoModifiers()) {
5485                    /*
5486                     * If there is a click listener, just call through to
5487                     * super, which will invoke it.
5488                     *
5489                     * If there isn't a click listener, try to show the soft
5490                     * input method.  (It will also
5491                     * call performClick(), but that won't do anything in
5492                     * this case.)
5493                     */
5494                    if (!hasOnClickListeners()) {
5495                        if (mMovement != null && mText instanceof Editable
5496                                && mLayout != null && onCheckIsTextEditor()) {
5497                            InputMethodManager imm = InputMethodManager.peekInstance();
5498                            viewClicked(imm);
5499                            if (imm != null) {
5500                                imm.showSoftInput(this, 0);
5501                            }
5502                        }
5503                    }
5504                }
5505                return super.onKeyUp(keyCode, event);
5506
5507            case KeyEvent.KEYCODE_ENTER:
5508                if (event.hasNoModifiers()) {
5509                    if (mInputContentType != null
5510                            && mInputContentType.onEditorActionListener != null
5511                            && mInputContentType.enterDown) {
5512                        mInputContentType.enterDown = false;
5513                        if (mInputContentType.onEditorActionListener.onEditorAction(
5514                                this, EditorInfo.IME_NULL, event)) {
5515                            return true;
5516                        }
5517                    }
5518
5519                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5520                            || shouldAdvanceFocusOnEnter()) {
5521                        /*
5522                         * If there is a click listener, just call through to
5523                         * super, which will invoke it.
5524                         *
5525                         * If there isn't a click listener, try to advance focus,
5526                         * but still call through to super, which will reset the
5527                         * pressed state and longpress state.  (It will also
5528                         * call performClick(), but that won't do anything in
5529                         * this case.)
5530                         */
5531                        if (!hasOnClickListeners()) {
5532                            View v = focusSearch(FOCUS_DOWN);
5533
5534                            if (v != null) {
5535                                if (!v.requestFocus(FOCUS_DOWN)) {
5536                                    throw new IllegalStateException(
5537                                            "focus search returned a view " +
5538                                            "that wasn't able to take focus!");
5539                                }
5540
5541                                /*
5542                                 * Return true because we handled the key; super
5543                                 * will return false because there was no click
5544                                 * listener.
5545                                 */
5546                                super.onKeyUp(keyCode, event);
5547                                return true;
5548                            } else if ((event.getFlags()
5549                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5550                                // No target for next focus, but make sure the IME
5551                                // if this came from it.
5552                                InputMethodManager imm = InputMethodManager.peekInstance();
5553                                if (imm != null && imm.isActive(this)) {
5554                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5555                                }
5556                            }
5557                        }
5558                    }
5559                    return super.onKeyUp(keyCode, event);
5560                }
5561                break;
5562        }
5563
5564        if (mInput != null)
5565            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
5566                return true;
5567
5568        if (mMovement != null && mLayout != null)
5569            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5570                return true;
5571
5572        return super.onKeyUp(keyCode, event);
5573    }
5574
5575    @Override
5576    public boolean onCheckIsTextEditor() {
5577        return mInputType != EditorInfo.TYPE_NULL;
5578    }
5579
5580    @Override
5581    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5582        if (onCheckIsTextEditor() && isEnabled()) {
5583            if (mInputMethodState == null) {
5584                mInputMethodState = new InputMethodState();
5585            }
5586            outAttrs.inputType = mInputType;
5587            if (mInputContentType != null) {
5588                outAttrs.imeOptions = mInputContentType.imeOptions;
5589                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
5590                outAttrs.actionLabel = mInputContentType.imeActionLabel;
5591                outAttrs.actionId = mInputContentType.imeActionId;
5592                outAttrs.extras = mInputContentType.extras;
5593            } else {
5594                outAttrs.imeOptions = EditorInfo.IME_NULL;
5595            }
5596            if (focusSearch(FOCUS_DOWN) != null) {
5597                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5598            }
5599            if (focusSearch(FOCUS_UP) != null) {
5600                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5601            }
5602            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5603                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
5604                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
5605                    // An action has not been set, but the enter key will move to
5606                    // the next focus, so set the action to that.
5607                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
5608                } else {
5609                    // An action has not been set, and there is no focus to move
5610                    // to, so let's just supply a "done" action.
5611                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
5612                }
5613                if (!shouldAdvanceFocusOnEnter()) {
5614                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5615                }
5616            }
5617            if (isMultilineInputType(outAttrs.inputType)) {
5618                // Multi-line text editors should always show an enter key.
5619                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5620            }
5621            outAttrs.hintText = mHint;
5622            if (mText instanceof Editable) {
5623                InputConnection ic = new EditableInputConnection(this);
5624                outAttrs.initialSelStart = getSelectionStart();
5625                outAttrs.initialSelEnd = getSelectionEnd();
5626                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
5627                return ic;
5628            }
5629        }
5630        return null;
5631    }
5632
5633    /**
5634     * If this TextView contains editable content, extract a portion of it
5635     * based on the information in <var>request</var> in to <var>outText</var>.
5636     * @return Returns true if the text was successfully extracted, else false.
5637     */
5638    public boolean extractText(ExtractedTextRequest request,
5639            ExtractedText outText) {
5640        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
5641                EXTRACT_UNKNOWN, outText);
5642    }
5643
5644    static final int EXTRACT_NOTHING = -2;
5645    static final int EXTRACT_UNKNOWN = -1;
5646
5647    boolean extractTextInternal(ExtractedTextRequest request,
5648            int partialStartOffset, int partialEndOffset, int delta,
5649            ExtractedText outText) {
5650        final CharSequence content = mText;
5651        if (content != null) {
5652            if (partialStartOffset != EXTRACT_NOTHING) {
5653                final int N = content.length();
5654                if (partialStartOffset < 0) {
5655                    outText.partialStartOffset = outText.partialEndOffset = -1;
5656                    partialStartOffset = 0;
5657                    partialEndOffset = N;
5658                } else {
5659                    // Now use the delta to determine the actual amount of text
5660                    // we need.
5661                    partialEndOffset += delta;
5662                    // Adjust offsets to ensure we contain full spans.
5663                    if (content instanceof Spanned) {
5664                        Spanned spanned = (Spanned)content;
5665                        Object[] spans = spanned.getSpans(partialStartOffset,
5666                                partialEndOffset, ParcelableSpan.class);
5667                        int i = spans.length;
5668                        while (i > 0) {
5669                            i--;
5670                            int j = spanned.getSpanStart(spans[i]);
5671                            if (j < partialStartOffset) partialStartOffset = j;
5672                            j = spanned.getSpanEnd(spans[i]);
5673                            if (j > partialEndOffset) partialEndOffset = j;
5674                        }
5675                    }
5676                    outText.partialStartOffset = partialStartOffset;
5677                    outText.partialEndOffset = partialEndOffset - delta;
5678
5679                    if (partialStartOffset > N) {
5680                        partialStartOffset = N;
5681                    } else if (partialStartOffset < 0) {
5682                        partialStartOffset = 0;
5683                    }
5684                    if (partialEndOffset > N) {
5685                        partialEndOffset = N;
5686                    } else if (partialEndOffset < 0) {
5687                        partialEndOffset = 0;
5688                    }
5689                }
5690                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
5691                    outText.text = content.subSequence(partialStartOffset,
5692                            partialEndOffset);
5693                } else {
5694                    outText.text = TextUtils.substring(content, partialStartOffset,
5695                            partialEndOffset);
5696                }
5697            } else {
5698                outText.partialStartOffset = 0;
5699                outText.partialEndOffset = 0;
5700                outText.text = "";
5701            }
5702            outText.flags = 0;
5703            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
5704                outText.flags |= ExtractedText.FLAG_SELECTING;
5705            }
5706            if (mSingleLine) {
5707                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
5708            }
5709            outText.startOffset = 0;
5710            outText.selectionStart = getSelectionStart();
5711            outText.selectionEnd = getSelectionEnd();
5712            return true;
5713        }
5714        return false;
5715    }
5716
5717    boolean reportExtractedText() {
5718        final InputMethodState ims = mInputMethodState;
5719        if (ims != null) {
5720            final boolean contentChanged = ims.mContentChanged;
5721            if (contentChanged || ims.mSelectionModeChanged) {
5722                ims.mContentChanged = false;
5723                ims.mSelectionModeChanged = false;
5724                final ExtractedTextRequest req = mInputMethodState.mExtracting;
5725                if (req != null) {
5726                    InputMethodManager imm = InputMethodManager.peekInstance();
5727                    if (imm != null) {
5728                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
5729                                + ims.mChangedStart + " end=" + ims.mChangedEnd
5730                                + " delta=" + ims.mChangedDelta);
5731                        if (ims.mChangedStart < 0 && !contentChanged) {
5732                            ims.mChangedStart = EXTRACT_NOTHING;
5733                        }
5734                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
5735                                ims.mChangedDelta, ims.mTmpExtracted)) {
5736                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
5737                                    + ims.mTmpExtracted.partialStartOffset
5738                                    + " end=" + ims.mTmpExtracted.partialEndOffset
5739                                    + ": " + ims.mTmpExtracted.text);
5740                            imm.updateExtractedText(this, req.token,
5741                                    mInputMethodState.mTmpExtracted);
5742                            ims.mChangedStart = EXTRACT_UNKNOWN;
5743                            ims.mChangedEnd = EXTRACT_UNKNOWN;
5744                            ims.mChangedDelta = 0;
5745                            ims.mContentChanged = false;
5746                            return true;
5747                        }
5748                    }
5749                }
5750            }
5751        }
5752        return false;
5753    }
5754
5755    /**
5756     * This is used to remove all style-impacting spans from text before new
5757     * extracted text is being replaced into it, so that we don't have any
5758     * lingering spans applied during the replace.
5759     */
5760    static void removeParcelableSpans(Spannable spannable, int start, int end) {
5761        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5762        int i = spans.length;
5763        while (i > 0) {
5764            i--;
5765            spannable.removeSpan(spans[i]);
5766        }
5767    }
5768
5769    /**
5770     * Apply to this text view the given extracted text, as previously
5771     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5772     */
5773    public void setExtractedText(ExtractedText text) {
5774        Editable content = getEditableText();
5775        if (text.text != null) {
5776            if (content == null) {
5777                setText(text.text, TextView.BufferType.EDITABLE);
5778            } else if (text.partialStartOffset < 0) {
5779                removeParcelableSpans(content, 0, content.length());
5780                content.replace(0, content.length(), text.text);
5781            } else {
5782                final int N = content.length();
5783                int start = text.partialStartOffset;
5784                if (start > N) start = N;
5785                int end = text.partialEndOffset;
5786                if (end > N) end = N;
5787                removeParcelableSpans(content, start, end);
5788                content.replace(start, end, text.text);
5789            }
5790        }
5791
5792        // Now set the selection position...  make sure it is in range, to
5793        // avoid crashes.  If this is a partial update, it is possible that
5794        // the underlying text may have changed, causing us problems here.
5795        // Also we just don't want to trust clients to do the right thing.
5796        Spannable sp = (Spannable)getText();
5797        final int N = sp.length();
5798        int start = text.selectionStart;
5799        if (start < 0) start = 0;
5800        else if (start > N) start = N;
5801        int end = text.selectionEnd;
5802        if (end < 0) end = 0;
5803        else if (end > N) end = N;
5804        Selection.setSelection(sp, start, end);
5805
5806        // Finally, update the selection mode.
5807        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5808            MetaKeyKeyListener.startSelecting(this, sp);
5809        } else {
5810            MetaKeyKeyListener.stopSelecting(this, sp);
5811        }
5812    }
5813
5814    /**
5815     * @hide
5816     */
5817    public void setExtracting(ExtractedTextRequest req) {
5818        if (mInputMethodState != null) {
5819            mInputMethodState.mExtracting = req;
5820        }
5821        // This would stop a possible selection mode, but no such mode is started in case
5822        // extracted mode will start. Some text is selected though, and will trigger an action mode
5823        // in the extracted view.
5824        hideControllers();
5825    }
5826
5827    /**
5828     * Called by the framework in response to a text completion from
5829     * the current input method, provided by it calling
5830     * {@link InputConnection#commitCompletion
5831     * InputConnection.commitCompletion()}.  The default implementation does
5832     * nothing; text views that are supporting auto-completion should override
5833     * this to do their desired behavior.
5834     *
5835     * @param text The auto complete text the user has selected.
5836     */
5837    public void onCommitCompletion(CompletionInfo text) {
5838        // intentionally empty
5839    }
5840
5841    /**
5842     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5843     * a dictionnary) from the current input method, provided by it calling
5844     * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5845     * implementation flashes the background of the corrected word to provide feedback to the user.
5846     *
5847     * @param info The auto correct info about the text that was corrected.
5848     */
5849    public void onCommitCorrection(CorrectionInfo info) {
5850        if (mCorrectionHighlighter == null) {
5851            mCorrectionHighlighter = new CorrectionHighlighter();
5852        } else {
5853            mCorrectionHighlighter.invalidate(false);
5854        }
5855
5856        mCorrectionHighlighter.highlight(info);
5857    }
5858
5859    private class CorrectionHighlighter {
5860        private final Path mPath = new Path();
5861        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
5862        private int mStart, mEnd;
5863        private long mFadingStartTime;
5864        private final static int FADE_OUT_DURATION = 400;
5865
5866        public CorrectionHighlighter() {
5867            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
5868            mPaint.setStyle(Paint.Style.FILL);
5869        }
5870
5871        public void highlight(CorrectionInfo info) {
5872            mStart = info.getOffset();
5873            mEnd = mStart + info.getNewText().length();
5874            mFadingStartTime = SystemClock.uptimeMillis();
5875
5876            if (mStart < 0 || mEnd < 0) {
5877                stopAnimation();
5878            }
5879        }
5880
5881        public void draw(Canvas canvas, int cursorOffsetVertical) {
5882            if (updatePath() && updatePaint()) {
5883                if (cursorOffsetVertical != 0) {
5884                    canvas.translate(0, cursorOffsetVertical);
5885                }
5886
5887                canvas.drawPath(mPath, mPaint);
5888
5889                if (cursorOffsetVertical != 0) {
5890                    canvas.translate(0, -cursorOffsetVertical);
5891                }
5892                invalidate(true); // TODO invalidate cursor region only
5893            } else {
5894                stopAnimation();
5895                invalidate(false); // TODO invalidate cursor region only
5896            }
5897        }
5898
5899        private boolean updatePaint() {
5900            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
5901            if (duration > FADE_OUT_DURATION) return false;
5902
5903            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
5904            final int highlightColorAlpha = Color.alpha(mHighlightColor);
5905            final int color = (mHighlightColor & 0x00FFFFFF) +
5906                    ((int) (highlightColorAlpha * coef) << 24);
5907            mPaint.setColor(color);
5908            return true;
5909        }
5910
5911        private boolean updatePath() {
5912            final Layout layout = TextView.this.mLayout;
5913            if (layout == null) return false;
5914
5915            // Update in case text is edited while the animation is run
5916            final int length = mText.length();
5917            int start = Math.min(length, mStart);
5918            int end = Math.min(length, mEnd);
5919
5920            mPath.reset();
5921            TextView.this.mLayout.getSelectionPath(start, end, mPath);
5922            return true;
5923        }
5924
5925        private void invalidate(boolean delayed) {
5926            if (TextView.this.mLayout == null) return;
5927
5928            synchronized (sTempRect) {
5929                mPath.computeBounds(sTempRect, false);
5930
5931                int left = getCompoundPaddingLeft();
5932                int top = getExtendedPaddingTop() + getVerticalOffset(true);
5933
5934                if (delayed) {
5935                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
5936                            left + (int) sTempRect.left, top + (int) sTempRect.top,
5937                            left + (int) sTempRect.right, top + (int) sTempRect.bottom);
5938                } else {
5939                    TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top,
5940                            (int) sTempRect.right, (int) sTempRect.bottom);
5941                }
5942            }
5943        }
5944
5945        private void stopAnimation() {
5946            TextView.this.mCorrectionHighlighter = null;
5947        }
5948    }
5949
5950    public void beginBatchEdit() {
5951        mInBatchEditControllers = true;
5952        final InputMethodState ims = mInputMethodState;
5953        if (ims != null) {
5954            int nesting = ++ims.mBatchEditNesting;
5955            if (nesting == 1) {
5956                ims.mCursorChanged = false;
5957                ims.mChangedDelta = 0;
5958                if (ims.mContentChanged) {
5959                    // We already have a pending change from somewhere else,
5960                    // so turn this into a full update.
5961                    ims.mChangedStart = 0;
5962                    ims.mChangedEnd = mText.length();
5963                } else {
5964                    ims.mChangedStart = EXTRACT_UNKNOWN;
5965                    ims.mChangedEnd = EXTRACT_UNKNOWN;
5966                    ims.mContentChanged = false;
5967                }
5968                onBeginBatchEdit();
5969            }
5970        }
5971    }
5972
5973    public void endBatchEdit() {
5974        mInBatchEditControllers = false;
5975        final InputMethodState ims = mInputMethodState;
5976        if (ims != null) {
5977            int nesting = --ims.mBatchEditNesting;
5978            if (nesting == 0) {
5979                finishBatchEdit(ims);
5980            }
5981        }
5982    }
5983
5984    void ensureEndedBatchEdit() {
5985        final InputMethodState ims = mInputMethodState;
5986        if (ims != null && ims.mBatchEditNesting != 0) {
5987            ims.mBatchEditNesting = 0;
5988            finishBatchEdit(ims);
5989        }
5990    }
5991
5992    void finishBatchEdit(final InputMethodState ims) {
5993        onEndBatchEdit();
5994
5995        if (ims.mContentChanged || ims.mSelectionModeChanged) {
5996            updateAfterEdit();
5997            reportExtractedText();
5998        } else if (ims.mCursorChanged) {
5999            // Cheezy way to get us to report the current cursor location.
6000            invalidateCursor();
6001        }
6002    }
6003
6004    void updateAfterEdit() {
6005        invalidate();
6006        int curs = getSelectionStart();
6007
6008        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6009            registerForPreDraw();
6010        }
6011
6012        if (curs >= 0) {
6013            mHighlightPathBogus = true;
6014            makeBlink();
6015            bringPointIntoView(curs);
6016        }
6017
6018        checkForResize();
6019    }
6020
6021    /**
6022     * Called by the framework in response to a request to begin a batch
6023     * of edit operations through a call to link {@link #beginBatchEdit()}.
6024     */
6025    public void onBeginBatchEdit() {
6026        // intentionally empty
6027    }
6028
6029    /**
6030     * Called by the framework in response to a request to end a batch
6031     * of edit operations through a call to link {@link #endBatchEdit}.
6032     */
6033    public void onEndBatchEdit() {
6034        // intentionally empty
6035    }
6036
6037    /**
6038     * Called by the framework in response to a private command from the
6039     * current method, provided by it calling
6040     * {@link InputConnection#performPrivateCommand
6041     * InputConnection.performPrivateCommand()}.
6042     *
6043     * @param action The action name of the command.
6044     * @param data Any additional data for the command.  This may be null.
6045     * @return Return true if you handled the command, else false.
6046     */
6047    public boolean onPrivateIMECommand(String action, Bundle data) {
6048        return false;
6049    }
6050
6051    private void nullLayouts() {
6052        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6053            mSavedLayout = (BoringLayout) mLayout;
6054        }
6055        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6056            mSavedHintLayout = (BoringLayout) mHintLayout;
6057        }
6058
6059        mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6060
6061        mBoring = mHintBoring = null;
6062
6063        // Since it depends on the value of mLayout
6064        prepareCursorControllers();
6065    }
6066
6067    /**
6068     * Make a new Layout based on the already-measured size of the view,
6069     * on the assumption that it was measured correctly at some point.
6070     */
6071    private void assumeLayout() {
6072        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6073
6074        if (width < 1) {
6075            width = 0;
6076        }
6077
6078        int physicalWidth = width;
6079
6080        if (mHorizontallyScrolling) {
6081            width = VERY_WIDE;
6082        }
6083
6084        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6085                      physicalWidth, false);
6086    }
6087
6088    @Override
6089    protected void resetResolvedLayoutDirection() {
6090        super.resetResolvedLayoutDirection();
6091
6092        if (mLayoutAlignment != null &&
6093                (mTextAlign == TextAlign.VIEW_START ||
6094                mTextAlign == TextAlign.VIEW_END)) {
6095            mLayoutAlignment = null;
6096        }
6097    }
6098
6099    private Layout.Alignment getLayoutAlignment() {
6100        if (mLayoutAlignment == null) {
6101            Layout.Alignment alignment;
6102            TextAlign textAlign = mTextAlign;
6103            switch (textAlign) {
6104                case INHERIT:
6105                    // fall through to gravity temporarily
6106                    // intention is to inherit value through view hierarchy.
6107                case GRAVITY:
6108                    switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6109                        case Gravity.START:
6110                            alignment = Layout.Alignment.ALIGN_NORMAL;
6111                            break;
6112                        case Gravity.END:
6113                            alignment = Layout.Alignment.ALIGN_OPPOSITE;
6114                            break;
6115                        case Gravity.LEFT:
6116                            alignment = Layout.Alignment.ALIGN_LEFT;
6117                            break;
6118                        case Gravity.RIGHT:
6119                            alignment = Layout.Alignment.ALIGN_RIGHT;
6120                            break;
6121                        case Gravity.CENTER_HORIZONTAL:
6122                            alignment = Layout.Alignment.ALIGN_CENTER;
6123                            break;
6124                        default:
6125                            alignment = Layout.Alignment.ALIGN_NORMAL;
6126                            break;
6127                    }
6128                    break;
6129                case TEXT_START:
6130                    alignment = Layout.Alignment.ALIGN_NORMAL;
6131                    break;
6132                case TEXT_END:
6133                    alignment = Layout.Alignment.ALIGN_OPPOSITE;
6134                    break;
6135                case CENTER:
6136                    alignment = Layout.Alignment.ALIGN_CENTER;
6137                    break;
6138                case VIEW_START:
6139                    alignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6140                            Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6141                    break;
6142                case VIEW_END:
6143                    alignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6144                            Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6145                    break;
6146                default:
6147                    alignment = Layout.Alignment.ALIGN_NORMAL;
6148                    break;
6149            }
6150            mLayoutAlignment = alignment;
6151        }
6152        return mLayoutAlignment;
6153    }
6154
6155    /**
6156     * The width passed in is now the desired layout width,
6157     * not the full view width with padding.
6158     * {@hide}
6159     */
6160    protected void makeNewLayout(int wantWidth, int hintWidth,
6161                                 BoringLayout.Metrics boring,
6162                                 BoringLayout.Metrics hintBoring,
6163                                 int ellipsisWidth, boolean bringIntoView) {
6164        stopMarquee();
6165
6166        // Update "old" cached values
6167        mOldMaximum = mMaximum;
6168        mOldMaxMode = mMaxMode;
6169
6170        mHighlightPathBogus = true;
6171
6172        if (wantWidth < 0) {
6173            wantWidth = 0;
6174        }
6175        if (hintWidth < 0) {
6176            hintWidth = 0;
6177        }
6178
6179        Layout.Alignment alignment = getLayoutAlignment();
6180        boolean shouldEllipsize = mEllipsize != null && mInput == null;
6181        final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6182                mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6183        TruncateAt effectiveEllipsize = mEllipsize;
6184        if (mEllipsize == TruncateAt.MARQUEE &&
6185                mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6186            effectiveEllipsize = TruncateAt.END_SMALL;
6187        }
6188
6189        if (mTextDir == null) {
6190            resolveTextDirection();
6191        }
6192
6193        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6194                effectiveEllipsize, effectiveEllipsize == mEllipsize);
6195        if (switchEllipsize) {
6196            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6197                    TruncateAt.END : TruncateAt.MARQUEE;
6198            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6199                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6200        }
6201
6202        shouldEllipsize = mEllipsize != null;
6203        mHintLayout = null;
6204
6205        if (mHint != null) {
6206            if (shouldEllipsize) hintWidth = wantWidth;
6207
6208            if (hintBoring == UNKNOWN_BORING) {
6209                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6210                                                   mHintBoring);
6211                if (hintBoring != null) {
6212                    mHintBoring = hintBoring;
6213                }
6214            }
6215
6216            if (hintBoring != null) {
6217                if (hintBoring.width <= hintWidth &&
6218                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6219                    if (mSavedHintLayout != null) {
6220                        mHintLayout = mSavedHintLayout.
6221                                replaceOrMake(mHint, mTextPaint,
6222                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6223                                hintBoring, mIncludePad);
6224                    } else {
6225                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6226                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6227                                hintBoring, mIncludePad);
6228                    }
6229
6230                    mSavedHintLayout = (BoringLayout) mHintLayout;
6231                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6232                    if (mSavedHintLayout != null) {
6233                        mHintLayout = mSavedHintLayout.
6234                                replaceOrMake(mHint, mTextPaint,
6235                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6236                                hintBoring, mIncludePad, mEllipsize,
6237                                ellipsisWidth);
6238                    } else {
6239                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6240                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6241                                hintBoring, mIncludePad, mEllipsize,
6242                                ellipsisWidth);
6243                    }
6244                } else if (shouldEllipsize) {
6245                    mHintLayout = new StaticLayout(mHint,
6246                                0, mHint.length(),
6247                                mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6248                                mSpacingAdd, mIncludePad, mEllipsize,
6249                                ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6250                } else {
6251                    mHintLayout = new StaticLayout(mHint, mTextPaint,
6252                            hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6253                            mIncludePad);
6254                }
6255            } else if (shouldEllipsize) {
6256                mHintLayout = new StaticLayout(mHint,
6257                            0, mHint.length(),
6258                            mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6259                            mSpacingAdd, mIncludePad, mEllipsize,
6260                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6261            } else {
6262                mHintLayout = new StaticLayout(mHint, mTextPaint,
6263                        hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6264                        mIncludePad);
6265            }
6266        }
6267
6268        if (bringIntoView) {
6269            registerForPreDraw();
6270        }
6271
6272        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6273            if (!compressText(ellipsisWidth)) {
6274                final int height = mLayoutParams.height;
6275                // If the size of the view does not depend on the size of the text, try to
6276                // start the marquee immediately
6277                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6278                    startMarquee();
6279                } else {
6280                    // Defer the start of the marquee until we know our width (see setFrame())
6281                    mRestartMarquee = true;
6282                }
6283            }
6284        }
6285
6286        // CursorControllers need a non-null mLayout
6287        prepareCursorControllers();
6288    }
6289
6290    private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6291            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6292            boolean useSaved) {
6293        Layout result = null;
6294        if (mText instanceof Spannable) {
6295            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6296                    alignment, mTextDir, mSpacingMult,
6297                    mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
6298                            ellipsisWidth);
6299        } else {
6300            if (boring == UNKNOWN_BORING) {
6301                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6302                if (boring != null) {
6303                    mBoring = boring;
6304                }
6305            }
6306
6307            if (boring != null) {
6308                if (boring.width <= wantWidth &&
6309                        (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6310                    if (useSaved && mSavedLayout != null) {
6311                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6312                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6313                                boring, mIncludePad);
6314                    } else {
6315                        result = BoringLayout.make(mTransformed, mTextPaint,
6316                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6317                                boring, mIncludePad);
6318                    }
6319
6320                    if (useSaved) {
6321                        mSavedLayout = (BoringLayout) result;
6322                    }
6323                } else if (shouldEllipsize && boring.width <= wantWidth) {
6324                    if (useSaved && mSavedLayout != null) {
6325                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6326                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6327                                boring, mIncludePad, effectiveEllipsize,
6328                                ellipsisWidth);
6329                    } else {
6330                        result = BoringLayout.make(mTransformed, mTextPaint,
6331                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
6332                                boring, mIncludePad, effectiveEllipsize,
6333                                ellipsisWidth);
6334                    }
6335                } else if (shouldEllipsize) {
6336                    result = new StaticLayout(mTransformed,
6337                            0, mTransformed.length(),
6338                            mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6339                            mSpacingAdd, mIncludePad, effectiveEllipsize,
6340                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6341                } else {
6342                    result = new StaticLayout(mTransformed, mTextPaint,
6343                            wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6344                            mIncludePad);
6345                }
6346            } else if (shouldEllipsize) {
6347                result = new StaticLayout(mTransformed,
6348                        0, mTransformed.length(),
6349                        mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6350                        mSpacingAdd, mIncludePad, effectiveEllipsize,
6351                        ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6352            } else {
6353                result = new StaticLayout(mTransformed, mTextPaint,
6354                        wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6355                        mIncludePad);
6356            }
6357        }
6358        return result;
6359    }
6360
6361    private boolean compressText(float width) {
6362        if (isHardwareAccelerated()) return false;
6363
6364        // Only compress the text if it hasn't been compressed by the previous pass
6365        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6366                mTextPaint.getTextScaleX() == 1.0f) {
6367            final float textWidth = mLayout.getLineWidth(0);
6368            final float overflow = (textWidth + 1.0f - width) / width;
6369            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6370                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6371                post(new Runnable() {
6372                    public void run() {
6373                        requestLayout();
6374                    }
6375                });
6376                return true;
6377            }
6378        }
6379
6380        return false;
6381    }
6382
6383    private static int desired(Layout layout) {
6384        int n = layout.getLineCount();
6385        CharSequence text = layout.getText();
6386        float max = 0;
6387
6388        // if any line was wrapped, we can't use it.
6389        // but it's ok for the last line not to have a newline
6390
6391        for (int i = 0; i < n - 1; i++) {
6392            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6393                return -1;
6394        }
6395
6396        for (int i = 0; i < n; i++) {
6397            max = Math.max(max, layout.getLineWidth(i));
6398        }
6399
6400        return (int) FloatMath.ceil(max);
6401    }
6402
6403    /**
6404     * Set whether the TextView includes extra top and bottom padding to make
6405     * room for accents that go above the normal ascent and descent.
6406     * The default is true.
6407     *
6408     * @attr ref android.R.styleable#TextView_includeFontPadding
6409     */
6410    public void setIncludeFontPadding(boolean includepad) {
6411        if (mIncludePad != includepad) {
6412            mIncludePad = includepad;
6413
6414            if (mLayout != null) {
6415                nullLayouts();
6416                requestLayout();
6417                invalidate();
6418            }
6419        }
6420    }
6421
6422    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6423
6424    @Override
6425    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6426        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6427        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6428        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6429        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6430
6431        int width;
6432        int height;
6433
6434        BoringLayout.Metrics boring = UNKNOWN_BORING;
6435        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6436
6437        if (mTextDir == null) {
6438            resolveTextDirection();
6439        }
6440
6441        int des = -1;
6442        boolean fromexisting = false;
6443
6444        if (widthMode == MeasureSpec.EXACTLY) {
6445            // Parent has told us how big to be. So be it.
6446            width = widthSize;
6447        } else {
6448            if (mLayout != null && mEllipsize == null) {
6449                des = desired(mLayout);
6450            }
6451
6452            if (des < 0) {
6453                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6454                if (boring != null) {
6455                    mBoring = boring;
6456                }
6457            } else {
6458                fromexisting = true;
6459            }
6460
6461            if (boring == null || boring == UNKNOWN_BORING) {
6462                if (des < 0) {
6463                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6464                }
6465
6466                width = des;
6467            } else {
6468                width = boring.width;
6469            }
6470
6471            final Drawables dr = mDrawables;
6472            if (dr != null) {
6473                width = Math.max(width, dr.mDrawableWidthTop);
6474                width = Math.max(width, dr.mDrawableWidthBottom);
6475            }
6476
6477            if (mHint != null) {
6478                int hintDes = -1;
6479                int hintWidth;
6480
6481                if (mHintLayout != null && mEllipsize == null) {
6482                    hintDes = desired(mHintLayout);
6483                }
6484
6485                if (hintDes < 0) {
6486                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
6487                    if (hintBoring != null) {
6488                        mHintBoring = hintBoring;
6489                    }
6490                }
6491
6492                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6493                    if (hintDes < 0) {
6494                        hintDes = (int) FloatMath.ceil(
6495                                Layout.getDesiredWidth(mHint, mTextPaint));
6496                    }
6497
6498                    hintWidth = hintDes;
6499                } else {
6500                    hintWidth = hintBoring.width;
6501                }
6502
6503                if (hintWidth > width) {
6504                    width = hintWidth;
6505                }
6506            }
6507
6508            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6509
6510            if (mMaxWidthMode == EMS) {
6511                width = Math.min(width, mMaxWidth * getLineHeight());
6512            } else {
6513                width = Math.min(width, mMaxWidth);
6514            }
6515
6516            if (mMinWidthMode == EMS) {
6517                width = Math.max(width, mMinWidth * getLineHeight());
6518            } else {
6519                width = Math.max(width, mMinWidth);
6520            }
6521
6522            // Check against our minimum width
6523            width = Math.max(width, getSuggestedMinimumWidth());
6524
6525            if (widthMode == MeasureSpec.AT_MOST) {
6526                width = Math.min(widthSize, width);
6527            }
6528        }
6529
6530        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6531        int unpaddedWidth = want;
6532
6533        if (mHorizontallyScrolling) want = VERY_WIDE;
6534
6535        int hintWant = want;
6536        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6537
6538        if (mLayout == null) {
6539            makeNewLayout(want, hintWant, boring, hintBoring,
6540                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6541        } else {
6542            final boolean layoutChanged = (mLayout.getWidth() != want) ||
6543                    (hintWidth != hintWant) ||
6544                    (mLayout.getEllipsizedWidth() !=
6545                            width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6546
6547            final boolean widthChanged = (mHint == null) &&
6548                    (mEllipsize == null) &&
6549                    (want > mLayout.getWidth()) &&
6550                    (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6551
6552            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6553
6554            if (layoutChanged || maximumChanged) {
6555                if (!maximumChanged && widthChanged) {
6556                    mLayout.increaseWidthTo(want);
6557                } else {
6558                    makeNewLayout(want, hintWant, boring, hintBoring,
6559                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6560                }
6561            } else {
6562                // Nothing has changed
6563            }
6564        }
6565
6566        if (heightMode == MeasureSpec.EXACTLY) {
6567            // Parent has told us how big to be. So be it.
6568            height = heightSize;
6569            mDesiredHeightAtMeasure = -1;
6570        } else {
6571            int desired = getDesiredHeight();
6572
6573            height = desired;
6574            mDesiredHeightAtMeasure = desired;
6575
6576            if (heightMode == MeasureSpec.AT_MOST) {
6577                height = Math.min(desired, heightSize);
6578            }
6579        }
6580
6581        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6582        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6583            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6584        }
6585
6586        /*
6587         * We didn't let makeNewLayout() register to bring the cursor into view,
6588         * so do it here if there is any possibility that it is needed.
6589         */
6590        if (mMovement != null ||
6591            mLayout.getWidth() > unpaddedWidth ||
6592            mLayout.getHeight() > unpaddedHeight) {
6593            registerForPreDraw();
6594        } else {
6595            scrollTo(0, 0);
6596        }
6597
6598        setMeasuredDimension(width, height);
6599    }
6600
6601    private int getDesiredHeight() {
6602        return Math.max(
6603                getDesiredHeight(mLayout, true),
6604                getDesiredHeight(mHintLayout, mEllipsize != null));
6605    }
6606
6607    private int getDesiredHeight(Layout layout, boolean cap) {
6608        if (layout == null) {
6609            return 0;
6610        }
6611
6612        int linecount = layout.getLineCount();
6613        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6614        int desired = layout.getLineTop(linecount);
6615
6616        final Drawables dr = mDrawables;
6617        if (dr != null) {
6618            desired = Math.max(desired, dr.mDrawableHeightLeft);
6619            desired = Math.max(desired, dr.mDrawableHeightRight);
6620        }
6621
6622        desired += pad;
6623
6624        if (mMaxMode == LINES) {
6625            /*
6626             * Don't cap the hint to a certain number of lines.
6627             * (Do cap it, though, if we have a maximum pixel height.)
6628             */
6629            if (cap) {
6630                if (linecount > mMaximum) {
6631                    desired = layout.getLineTop(mMaximum);
6632
6633                    if (dr != null) {
6634                        desired = Math.max(desired, dr.mDrawableHeightLeft);
6635                        desired = Math.max(desired, dr.mDrawableHeightRight);
6636                    }
6637
6638                    desired += pad;
6639                    linecount = mMaximum;
6640                }
6641            }
6642        } else {
6643            desired = Math.min(desired, mMaximum);
6644        }
6645
6646        if (mMinMode == LINES) {
6647            if (linecount < mMinimum) {
6648                desired += getLineHeight() * (mMinimum - linecount);
6649            }
6650        } else {
6651            desired = Math.max(desired, mMinimum);
6652        }
6653
6654        // Check against our minimum height
6655        desired = Math.max(desired, getSuggestedMinimumHeight());
6656
6657        return desired;
6658    }
6659
6660    /**
6661     * Check whether a change to the existing text layout requires a
6662     * new view layout.
6663     */
6664    private void checkForResize() {
6665        boolean sizeChanged = false;
6666
6667        if (mLayout != null) {
6668            // Check if our width changed
6669            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6670                sizeChanged = true;
6671                invalidate();
6672            }
6673
6674            // Check if our height changed
6675            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6676                int desiredHeight = getDesiredHeight();
6677
6678                if (desiredHeight != this.getHeight()) {
6679                    sizeChanged = true;
6680                }
6681            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6682                if (mDesiredHeightAtMeasure >= 0) {
6683                    int desiredHeight = getDesiredHeight();
6684
6685                    if (desiredHeight != mDesiredHeightAtMeasure) {
6686                        sizeChanged = true;
6687                    }
6688                }
6689            }
6690        }
6691
6692        if (sizeChanged) {
6693            requestLayout();
6694            // caller will have already invalidated
6695        }
6696    }
6697
6698    /**
6699     * Check whether entirely new text requires a new view layout
6700     * or merely a new text layout.
6701     */
6702    private void checkForRelayout() {
6703        // If we have a fixed width, we can just swap in a new text layout
6704        // if the text height stays the same or if the view height is fixed.
6705
6706        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6707                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6708                (mHint == null || mHintLayout != null) &&
6709                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6710            // Static width, so try making a new text layout.
6711
6712            int oldht = mLayout.getHeight();
6713            int want = mLayout.getWidth();
6714            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6715
6716            /*
6717             * No need to bring the text into view, since the size is not
6718             * changing (unless we do the requestLayout(), in which case it
6719             * will happen at measure).
6720             */
6721            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6722                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6723                          false);
6724
6725            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6726                // In a fixed-height view, so use our new text layout.
6727                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6728                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6729                    invalidate();
6730                    return;
6731                }
6732
6733                // Dynamic height, but height has stayed the same,
6734                // so use our new text layout.
6735                if (mLayout.getHeight() == oldht &&
6736                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6737                    invalidate();
6738                    return;
6739                }
6740            }
6741
6742            // We lose: the height has changed and we have a dynamic height.
6743            // Request a new view layout using our new text layout.
6744            requestLayout();
6745            invalidate();
6746        } else {
6747            // Dynamic width, so we have no choice but to request a new
6748            // view layout with a new text layout.
6749            nullLayouts();
6750            requestLayout();
6751            invalidate();
6752        }
6753    }
6754
6755    @Override
6756    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6757        super.onLayout(changed, left, top, right, bottom);
6758        if (changed) mTextDisplayListIsValid = false;
6759    }
6760
6761    /**
6762     * Returns true if anything changed.
6763     */
6764    private boolean bringTextIntoView() {
6765        int line = 0;
6766        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6767            line = mLayout.getLineCount() - 1;
6768        }
6769
6770        Layout.Alignment a = mLayout.getParagraphAlignment(line);
6771        int dir = mLayout.getParagraphDirection(line);
6772        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6773        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6774        int ht = mLayout.getHeight();
6775
6776        int scrollx, scrolly;
6777
6778        // Convert to left, center, or right alignment.
6779        if (a == Layout.Alignment.ALIGN_NORMAL) {
6780            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6781                Layout.Alignment.ALIGN_RIGHT;
6782        } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6783            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6784                Layout.Alignment.ALIGN_LEFT;
6785        }
6786
6787        if (a == Layout.Alignment.ALIGN_CENTER) {
6788            /*
6789             * Keep centered if possible, or, if it is too wide to fit,
6790             * keep leading edge in view.
6791             */
6792
6793            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6794            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6795
6796            if (right - left < hspace) {
6797                scrollx = (right + left) / 2 - hspace / 2;
6798            } else {
6799                if (dir < 0) {
6800                    scrollx = right - hspace;
6801                } else {
6802                    scrollx = left;
6803                }
6804            }
6805        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
6806            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6807            scrollx = right - hspace;
6808        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
6809            scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6810        }
6811
6812        if (ht < vspace) {
6813            scrolly = 0;
6814        } else {
6815            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6816                scrolly = ht - vspace;
6817            } else {
6818                scrolly = 0;
6819            }
6820        }
6821
6822        if (scrollx != mScrollX || scrolly != mScrollY) {
6823            scrollTo(scrollx, scrolly);
6824            return true;
6825        } else {
6826            return false;
6827        }
6828    }
6829
6830    /**
6831     * Move the point, specified by the offset, into the view if it is needed.
6832     * This has to be called after layout. Returns true if anything changed.
6833     */
6834    public boolean bringPointIntoView(int offset) {
6835        boolean changed = false;
6836
6837        if (mLayout == null) return changed;
6838
6839        int line = mLayout.getLineForOffset(offset);
6840
6841        // FIXME: Is it okay to truncate this, or should we round?
6842        final int x = (int)mLayout.getPrimaryHorizontal(offset);
6843        final int top = mLayout.getLineTop(line);
6844        final int bottom = mLayout.getLineTop(line + 1);
6845
6846        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6847        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6848        int ht = mLayout.getHeight();
6849
6850        int grav;
6851
6852        switch (mLayout.getParagraphAlignment(line)) {
6853            case ALIGN_LEFT:
6854                grav = 1;
6855                break;
6856            case ALIGN_RIGHT:
6857                grav = -1;
6858                break;
6859            case ALIGN_NORMAL:
6860                grav = mLayout.getParagraphDirection(line);
6861                break;
6862            case ALIGN_OPPOSITE:
6863                grav = -mLayout.getParagraphDirection(line);
6864                break;
6865            case ALIGN_CENTER:
6866            default:
6867                grav = 0;
6868                break;
6869        }
6870
6871        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6872        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6873
6874        int hslack = (bottom - top) / 2;
6875        int vslack = hslack;
6876
6877        if (vslack > vspace / 4)
6878            vslack = vspace / 4;
6879        if (hslack > hspace / 4)
6880            hslack = hspace / 4;
6881
6882        int hs = mScrollX;
6883        int vs = mScrollY;
6884
6885        if (top - vs < vslack)
6886            vs = top - vslack;
6887        if (bottom - vs > vspace - vslack)
6888            vs = bottom - (vspace - vslack);
6889        if (ht - vs < vspace)
6890            vs = ht - vspace;
6891        if (0 - vs > 0)
6892            vs = 0;
6893
6894        if (grav != 0) {
6895            if (x - hs < hslack) {
6896                hs = x - hslack;
6897            }
6898            if (x - hs > hspace - hslack) {
6899                hs = x - (hspace - hslack);
6900            }
6901        }
6902
6903        if (grav < 0) {
6904            if (left - hs > 0)
6905                hs = left;
6906            if (right - hs < hspace)
6907                hs = right - hspace;
6908        } else if (grav > 0) {
6909            if (right - hs < hspace)
6910                hs = right - hspace;
6911            if (left - hs > 0)
6912                hs = left;
6913        } else /* grav == 0 */ {
6914            if (right - left <= hspace) {
6915                /*
6916                 * If the entire text fits, center it exactly.
6917                 */
6918                hs = left - (hspace - (right - left)) / 2;
6919            } else if (x > right - hslack) {
6920                /*
6921                 * If we are near the right edge, keep the right edge
6922                 * at the edge of the view.
6923                 */
6924                hs = right - hspace;
6925            } else if (x < left + hslack) {
6926                /*
6927                 * If we are near the left edge, keep the left edge
6928                 * at the edge of the view.
6929                 */
6930                hs = left;
6931            } else if (left > hs) {
6932                /*
6933                 * Is there whitespace visible at the left?  Fix it if so.
6934                 */
6935                hs = left;
6936            } else if (right < hs + hspace) {
6937                /*
6938                 * Is there whitespace visible at the right?  Fix it if so.
6939                 */
6940                hs = right - hspace;
6941            } else {
6942                /*
6943                 * Otherwise, float as needed.
6944                 */
6945                if (x - hs < hslack) {
6946                    hs = x - hslack;
6947                }
6948                if (x - hs > hspace - hslack) {
6949                    hs = x - (hspace - hslack);
6950                }
6951            }
6952        }
6953
6954        if (hs != mScrollX || vs != mScrollY) {
6955            if (mScroller == null) {
6956                scrollTo(hs, vs);
6957            } else {
6958                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6959                int dx = hs - mScrollX;
6960                int dy = vs - mScrollY;
6961
6962                if (duration > ANIMATED_SCROLL_GAP) {
6963                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
6964                    awakenScrollBars(mScroller.getDuration());
6965                    invalidate();
6966                } else {
6967                    if (!mScroller.isFinished()) {
6968                        mScroller.abortAnimation();
6969                    }
6970
6971                    scrollBy(dx, dy);
6972                }
6973
6974                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6975            }
6976
6977            changed = true;
6978        }
6979
6980        if (isFocused()) {
6981            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6982            // requestRectangleOnScreen() is in terms of content coordinates.
6983
6984            if (mTempRect == null) mTempRect = new Rect();
6985            // The offsets here are to ensure the rectangle we are using is
6986            // within our view bounds, in case the cursor is on the far left
6987            // or right.  If it isn't withing the bounds, then this request
6988            // will be ignored.
6989            mTempRect.set(x - 2, top, x + 2, bottom);
6990            getInterestingRect(mTempRect, line);
6991            mTempRect.offset(mScrollX, mScrollY);
6992
6993            if (requestRectangleOnScreen(mTempRect)) {
6994                changed = true;
6995            }
6996        }
6997
6998        return changed;
6999    }
7000
7001    /**
7002     * Move the cursor, if needed, so that it is at an offset that is visible
7003     * to the user.  This will not move the cursor if it represents more than
7004     * one character (a selection range).  This will only work if the
7005     * TextView contains spannable text; otherwise it will do nothing.
7006     *
7007     * @return True if the cursor was actually moved, false otherwise.
7008     */
7009    public boolean moveCursorToVisibleOffset() {
7010        if (!(mText instanceof Spannable)) {
7011            return false;
7012        }
7013        int start = getSelectionStart();
7014        int end = getSelectionEnd();
7015        if (start != end) {
7016            return false;
7017        }
7018
7019        // First: make sure the line is visible on screen:
7020
7021        int line = mLayout.getLineForOffset(start);
7022
7023        final int top = mLayout.getLineTop(line);
7024        final int bottom = mLayout.getLineTop(line + 1);
7025        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7026        int vslack = (bottom - top) / 2;
7027        if (vslack > vspace / 4)
7028            vslack = vspace / 4;
7029        final int vs = mScrollY;
7030
7031        if (top < (vs+vslack)) {
7032            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7033        } else if (bottom > (vspace+vs-vslack)) {
7034            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7035        }
7036
7037        // Next: make sure the character is visible on screen:
7038
7039        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7040        final int hs = mScrollX;
7041        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7042        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7043
7044        // line might contain bidirectional text
7045        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7046        final int highChar = leftChar > rightChar ? leftChar : rightChar;
7047
7048        int newStart = start;
7049        if (newStart < lowChar) {
7050            newStart = lowChar;
7051        } else if (newStart > highChar) {
7052            newStart = highChar;
7053        }
7054
7055        if (newStart != start) {
7056            Selection.setSelection((Spannable)mText, newStart);
7057            return true;
7058        }
7059
7060        return false;
7061    }
7062
7063    @Override
7064    public void computeScroll() {
7065        if (mScroller != null) {
7066            if (mScroller.computeScrollOffset()) {
7067                mScrollX = mScroller.getCurrX();
7068                mScrollY = mScroller.getCurrY();
7069                invalidateParentCaches();
7070                postInvalidate();  // So we draw again
7071            }
7072        }
7073    }
7074
7075    private void getInterestingRect(Rect r, int line) {
7076        convertFromViewportToContentCoordinates(r);
7077
7078        // Rectangle can can be expanded on first and last line to take
7079        // padding into account.
7080        // TODO Take left/right padding into account too?
7081        if (line == 0) r.top -= getExtendedPaddingTop();
7082        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7083    }
7084
7085    private void convertFromViewportToContentCoordinates(Rect r) {
7086        final int horizontalOffset = viewportToContentHorizontalOffset();
7087        r.left += horizontalOffset;
7088        r.right += horizontalOffset;
7089
7090        final int verticalOffset = viewportToContentVerticalOffset();
7091        r.top += verticalOffset;
7092        r.bottom += verticalOffset;
7093    }
7094
7095    private int viewportToContentHorizontalOffset() {
7096        return getCompoundPaddingLeft() - mScrollX;
7097    }
7098
7099    private int viewportToContentVerticalOffset() {
7100        int offset = getExtendedPaddingTop() - mScrollY;
7101        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7102            offset += getVerticalOffset(false);
7103        }
7104        return offset;
7105    }
7106
7107    @Override
7108    public void debug(int depth) {
7109        super.debug(depth);
7110
7111        String output = debugIndent(depth);
7112        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7113                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7114                + "} ";
7115
7116        if (mText != null) {
7117
7118            output += "mText=\"" + mText + "\" ";
7119            if (mLayout != null) {
7120                output += "mLayout width=" + mLayout.getWidth()
7121                        + " height=" + mLayout.getHeight();
7122            }
7123        } else {
7124            output += "mText=NULL";
7125        }
7126        Log.d(VIEW_LOG_TAG, output);
7127    }
7128
7129    /**
7130     * Convenience for {@link Selection#getSelectionStart}.
7131     */
7132    @ViewDebug.ExportedProperty(category = "text")
7133    public int getSelectionStart() {
7134        return Selection.getSelectionStart(getText());
7135    }
7136
7137    /**
7138     * Convenience for {@link Selection#getSelectionEnd}.
7139     */
7140    @ViewDebug.ExportedProperty(category = "text")
7141    public int getSelectionEnd() {
7142        return Selection.getSelectionEnd(getText());
7143    }
7144
7145    /**
7146     * Return true iff there is a selection inside this text view.
7147     */
7148    public boolean hasSelection() {
7149        final int selectionStart = getSelectionStart();
7150        final int selectionEnd = getSelectionEnd();
7151
7152        return selectionStart >= 0 && selectionStart != selectionEnd;
7153    }
7154
7155    /**
7156     * Sets the properties of this field (lines, horizontally scrolling,
7157     * transformation method) to be for a single-line input.
7158     *
7159     * @attr ref android.R.styleable#TextView_singleLine
7160     */
7161    public void setSingleLine() {
7162        setSingleLine(true);
7163    }
7164
7165    /**
7166     * Sets the properties of this field to transform input to ALL CAPS
7167     * display. This may use a "small caps" formatting if available.
7168     * This setting will be ignored if this field is editable or selectable.
7169     *
7170     * This call replaces the current transformation method. Disabling this
7171     * will not necessarily restore the previous behavior from before this
7172     * was enabled.
7173     *
7174     * @see #setTransformationMethod(TransformationMethod)
7175     * @attr ref android.R.styleable#TextView_textAllCaps
7176     */
7177    public void setAllCaps(boolean allCaps) {
7178        if (allCaps) {
7179            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7180        } else {
7181            setTransformationMethod(null);
7182        }
7183    }
7184
7185    /**
7186     * If true, sets the properties of this field (number of lines, horizontally scrolling,
7187     * transformation method) to be for a single-line input; if false, restores these to the default
7188     * conditions.
7189     *
7190     * Note that the default conditions are not necessarily those that were in effect prior this
7191     * method, and you may want to reset these properties to your custom values.
7192     *
7193     * @attr ref android.R.styleable#TextView_singleLine
7194     */
7195    @android.view.RemotableViewMethod
7196    public void setSingleLine(boolean singleLine) {
7197        // Could be used, but may break backward compatibility.
7198        // if (mSingleLine == singleLine) return;
7199        setInputTypeSingleLine(singleLine);
7200        applySingleLine(singleLine, true, true);
7201    }
7202
7203    /**
7204     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7205     * @param singleLine
7206     */
7207    private void setInputTypeSingleLine(boolean singleLine) {
7208        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7209            if (singleLine) {
7210                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7211            } else {
7212                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7213            }
7214        }
7215    }
7216
7217    private void applySingleLine(boolean singleLine, boolean applyTransformation,
7218            boolean changeMaxLines) {
7219        mSingleLine = singleLine;
7220        if (singleLine) {
7221            setLines(1);
7222            setHorizontallyScrolling(true);
7223            if (applyTransformation) {
7224                setTransformationMethod(SingleLineTransformationMethod.getInstance());
7225            }
7226        } else {
7227            if (changeMaxLines) {
7228                setMaxLines(Integer.MAX_VALUE);
7229            }
7230            setHorizontallyScrolling(false);
7231            if (applyTransformation) {
7232                setTransformationMethod(null);
7233            }
7234        }
7235    }
7236
7237    /**
7238     * Causes words in the text that are longer than the view is wide
7239     * to be ellipsized instead of broken in the middle.  You may also
7240     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7241     * to constrain the text to a single line.  Use <code>null</code>
7242     * to turn off ellipsizing.
7243     *
7244     * If {@link #setMaxLines} has been used to set two or more lines,
7245     * {@link android.text.TextUtils.TruncateAt#END} and
7246     * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7247     * (other ellipsizing types will not do anything).
7248     *
7249     * @attr ref android.R.styleable#TextView_ellipsize
7250     */
7251    public void setEllipsize(TextUtils.TruncateAt where) {
7252        // TruncateAt is an enum. != comparison is ok between these singleton objects.
7253        if (mEllipsize != where) {
7254            mEllipsize = where;
7255
7256            if (mLayout != null) {
7257                nullLayouts();
7258                requestLayout();
7259                invalidate();
7260            }
7261        }
7262    }
7263
7264    /**
7265     * Sets how many times to repeat the marquee animation. Only applied if the
7266     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7267     *
7268     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7269     */
7270    public void setMarqueeRepeatLimit(int marqueeLimit) {
7271        mMarqueeRepeatLimit = marqueeLimit;
7272    }
7273
7274    /**
7275     * Returns where, if anywhere, words that are longer than the view
7276     * is wide should be ellipsized.
7277     */
7278    @ViewDebug.ExportedProperty
7279    public TextUtils.TruncateAt getEllipsize() {
7280        return mEllipsize;
7281    }
7282
7283    /**
7284     * Set the TextView so that when it takes focus, all the text is
7285     * selected.
7286     *
7287     * @attr ref android.R.styleable#TextView_selectAllOnFocus
7288     */
7289    @android.view.RemotableViewMethod
7290    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7291        mSelectAllOnFocus = selectAllOnFocus;
7292
7293        if (selectAllOnFocus && !(mText instanceof Spannable)) {
7294            setText(mText, BufferType.SPANNABLE);
7295        }
7296    }
7297
7298    /**
7299     * Set whether the cursor is visible.  The default is true.
7300     *
7301     * @attr ref android.R.styleable#TextView_cursorVisible
7302     */
7303    @android.view.RemotableViewMethod
7304    public void setCursorVisible(boolean visible) {
7305        if (mCursorVisible != visible) {
7306            mCursorVisible = visible;
7307            invalidate();
7308
7309            makeBlink();
7310
7311            // InsertionPointCursorController depends on mCursorVisible
7312            prepareCursorControllers();
7313        }
7314    }
7315
7316    private boolean isCursorVisible() {
7317        return mCursorVisible && isTextEditable();
7318    }
7319
7320    private boolean canMarquee() {
7321        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7322        return width > 0 && (mLayout.getLineWidth(0) > width ||
7323                (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7324                        mSavedMarqueeModeLayout.getLineWidth(0) > width));
7325    }
7326
7327    private void startMarquee() {
7328        // Do not ellipsize EditText
7329        if (mInput != null) return;
7330
7331        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7332            return;
7333        }
7334
7335        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7336                getLineCount() == 1 && canMarquee()) {
7337
7338            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7339                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7340                final Layout tmp = mLayout;
7341                mLayout = mSavedMarqueeModeLayout;
7342                mSavedMarqueeModeLayout = tmp;
7343                setHorizontalFadingEdgeEnabled(true);
7344                requestLayout();
7345                invalidate();
7346            }
7347
7348            if (mMarquee == null) mMarquee = new Marquee(this);
7349            mMarquee.start(mMarqueeRepeatLimit);
7350        }
7351    }
7352
7353    private void stopMarquee() {
7354        if (mMarquee != null && !mMarquee.isStopped()) {
7355            mMarquee.stop();
7356        }
7357
7358        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7359            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7360            final Layout tmp = mSavedMarqueeModeLayout;
7361            mSavedMarqueeModeLayout = mLayout;
7362            mLayout = tmp;
7363            setHorizontalFadingEdgeEnabled(false);
7364            requestLayout();
7365            invalidate();
7366        }
7367    }
7368
7369    private void startStopMarquee(boolean start) {
7370        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7371            if (start) {
7372                startMarquee();
7373            } else {
7374                stopMarquee();
7375            }
7376        }
7377    }
7378
7379    private static final class Marquee extends Handler {
7380        // TODO: Add an option to configure this
7381        private static final float MARQUEE_DELTA_MAX = 0.07f;
7382        private static final int MARQUEE_DELAY = 1200;
7383        private static final int MARQUEE_RESTART_DELAY = 1200;
7384        private static final int MARQUEE_RESOLUTION = 1000 / 30;
7385        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
7386
7387        private static final byte MARQUEE_STOPPED = 0x0;
7388        private static final byte MARQUEE_STARTING = 0x1;
7389        private static final byte MARQUEE_RUNNING = 0x2;
7390
7391        private static final int MESSAGE_START = 0x1;
7392        private static final int MESSAGE_TICK = 0x2;
7393        private static final int MESSAGE_RESTART = 0x3;
7394
7395        private final WeakReference<TextView> mView;
7396
7397        private byte mStatus = MARQUEE_STOPPED;
7398        private final float mScrollUnit;
7399        private float mMaxScroll;
7400        float mMaxFadeScroll;
7401        private float mGhostStart;
7402        private float mGhostOffset;
7403        private float mFadeStop;
7404        private int mRepeatLimit;
7405
7406        float mScroll;
7407
7408        Marquee(TextView v) {
7409            final float density = v.getContext().getResources().getDisplayMetrics().density;
7410            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
7411            mView = new WeakReference<TextView>(v);
7412        }
7413
7414        @Override
7415        public void handleMessage(Message msg) {
7416            switch (msg.what) {
7417                case MESSAGE_START:
7418                    mStatus = MARQUEE_RUNNING;
7419                    tick();
7420                    break;
7421                case MESSAGE_TICK:
7422                    tick();
7423                    break;
7424                case MESSAGE_RESTART:
7425                    if (mStatus == MARQUEE_RUNNING) {
7426                        if (mRepeatLimit >= 0) {
7427                            mRepeatLimit--;
7428                        }
7429                        start(mRepeatLimit);
7430                    }
7431                    break;
7432            }
7433        }
7434
7435        void tick() {
7436            if (mStatus != MARQUEE_RUNNING) {
7437                return;
7438            }
7439
7440            removeMessages(MESSAGE_TICK);
7441
7442            final TextView textView = mView.get();
7443            if (textView != null && (textView.isFocused() || textView.isSelected())) {
7444                mScroll += mScrollUnit;
7445                if (mScroll > mMaxScroll) {
7446                    mScroll = mMaxScroll;
7447                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
7448                } else {
7449                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
7450                }
7451                textView.invalidate();
7452            }
7453        }
7454
7455        void stop() {
7456            mStatus = MARQUEE_STOPPED;
7457            removeMessages(MESSAGE_START);
7458            removeMessages(MESSAGE_RESTART);
7459            removeMessages(MESSAGE_TICK);
7460            resetScroll();
7461        }
7462
7463        private void resetScroll() {
7464            mScroll = 0.0f;
7465            final TextView textView = mView.get();
7466            if (textView != null) textView.invalidate();
7467        }
7468
7469        void start(int repeatLimit) {
7470            if (repeatLimit == 0) {
7471                stop();
7472                return;
7473            }
7474            mRepeatLimit = repeatLimit;
7475            final TextView textView = mView.get();
7476            if (textView != null && textView.mLayout != null) {
7477                mStatus = MARQUEE_STARTING;
7478                mScroll = 0.0f;
7479                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
7480                        textView.getCompoundPaddingRight();
7481                final float lineWidth = textView.mLayout.getLineWidth(0);
7482                final float gap = textWidth / 3.0f;
7483                mGhostStart = lineWidth - textWidth + gap;
7484                mMaxScroll = mGhostStart + textWidth;
7485                mGhostOffset = lineWidth + gap;
7486                mFadeStop = lineWidth + textWidth / 6.0f;
7487                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
7488
7489                textView.invalidate();
7490                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
7491            }
7492        }
7493
7494        float getGhostOffset() {
7495            return mGhostOffset;
7496        }
7497
7498        boolean shouldDrawLeftFade() {
7499            return mScroll <= mFadeStop;
7500        }
7501
7502        boolean shouldDrawGhost() {
7503            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
7504        }
7505
7506        boolean isRunning() {
7507            return mStatus == MARQUEE_RUNNING;
7508        }
7509
7510        boolean isStopped() {
7511            return mStatus == MARQUEE_STOPPED;
7512        }
7513    }
7514
7515    /**
7516     * This method is called when the text is changed, in case any subclasses
7517     * would like to know.
7518     *
7519     * Within <code>text</code>, the <code>lengthAfter</code> characters
7520     * beginning at <code>start</code> have just replaced old text that had
7521     * length <code>lengthBefore</code>. It is an error to attempt to make
7522     * changes to <code>text</code> from this callback.
7523     *
7524     * @param text The text the TextView is displaying
7525     * @param start The offset of the start of the range of the text that was
7526     * modified
7527     * @param lengthBefore The length of the former text that has been replaced
7528     * @param lengthAfter The length of the replacement modified text
7529     */
7530    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7531        // intentionally empty, template pattern method can be overridden by subclasses
7532    }
7533
7534    /**
7535     * This method is called when the selection has changed, in case any
7536     * subclasses would like to know.
7537     *
7538     * @param selStart The new selection start location.
7539     * @param selEnd The new selection end location.
7540     */
7541    protected void onSelectionChanged(int selStart, int selEnd) {
7542        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7543        mTextDisplayListIsValid = false;
7544    }
7545
7546    /**
7547     * Adds a TextWatcher to the list of those whose methods are called
7548     * whenever this TextView's text changes.
7549     * <p>
7550     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7551     * not called after {@link #setText} calls.  Now, doing {@link #setText}
7552     * if there are any text changed listeners forces the buffer type to
7553     * Editable if it would not otherwise be and does call this method.
7554     */
7555    public void addTextChangedListener(TextWatcher watcher) {
7556        if (mListeners == null) {
7557            mListeners = new ArrayList<TextWatcher>();
7558        }
7559
7560        mListeners.add(watcher);
7561    }
7562
7563    /**
7564     * Removes the specified TextWatcher from the list of those whose
7565     * methods are called
7566     * whenever this TextView's text changes.
7567     */
7568    public void removeTextChangedListener(TextWatcher watcher) {
7569        if (mListeners != null) {
7570            int i = mListeners.indexOf(watcher);
7571
7572            if (i >= 0) {
7573                mListeners.remove(i);
7574            }
7575        }
7576    }
7577
7578    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7579        if (mListeners != null) {
7580            final ArrayList<TextWatcher> list = mListeners;
7581            final int count = list.size();
7582            for (int i = 0; i < count; i++) {
7583                list.get(i).beforeTextChanged(text, start, before, after);
7584            }
7585        }
7586
7587        // The spans that are inside or intersect the modified region no longer make sense
7588        removeIntersectingSpans(start, start + before, SpellCheckSpan.class);
7589        removeIntersectingSpans(start, start + before, SuggestionSpan.class);
7590    }
7591
7592    // Removes all spans that are inside or actually overlap the start..end range
7593    private <T> void removeIntersectingSpans(int start, int end, Class<T> type) {
7594        if (!(mText instanceof Editable)) return;
7595        Editable text = (Editable) mText;
7596
7597        T[] spans = text.getSpans(start, end, type);
7598        final int length = spans.length;
7599        for (int i = 0; i < length; i++) {
7600            final int s = text.getSpanStart(spans[i]);
7601            final int e = text.getSpanEnd(spans[i]);
7602            // Spans that are adjacent to the edited region will be handled in
7603            // updateSpellCheckSpans. Result depends on what will be added (space or text)
7604            if (e == start || s == end) break;
7605            text.removeSpan(spans[i]);
7606        }
7607    }
7608
7609    /**
7610     * Not private so it can be called from an inner class without going
7611     * through a thunk.
7612     */
7613    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7614        if (mListeners != null) {
7615            final ArrayList<TextWatcher> list = mListeners;
7616            final int count = list.size();
7617            for (int i = 0; i < count; i++) {
7618                list.get(i).onTextChanged(text, start, before, after);
7619            }
7620        }
7621
7622        updateSpellCheckSpans(start, start + after, false);
7623        mTextDisplayListIsValid = false;
7624
7625        // Hide the controllers as soon as text is modified (typing, procedural...)
7626        // We do not hide the span controllers, since they can be added when a new text is
7627        // inserted into the text view (voice IME).
7628        hideCursorControllers();
7629    }
7630
7631    /**
7632     * Not private so it can be called from an inner class without going
7633     * through a thunk.
7634     */
7635    void sendAfterTextChanged(Editable text) {
7636        if (mListeners != null) {
7637            final ArrayList<TextWatcher> list = mListeners;
7638            final int count = list.size();
7639            for (int i = 0; i < count; i++) {
7640                list.get(i).afterTextChanged(text);
7641            }
7642        }
7643    }
7644
7645    /**
7646     * Not private so it can be called from an inner class without going
7647     * through a thunk.
7648     */
7649    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
7650        final InputMethodState ims = mInputMethodState;
7651        if (ims == null || ims.mBatchEditNesting == 0) {
7652            updateAfterEdit();
7653        }
7654        if (ims != null) {
7655            ims.mContentChanged = true;
7656            if (ims.mChangedStart < 0) {
7657                ims.mChangedStart = start;
7658                ims.mChangedEnd = start+before;
7659            } else {
7660                ims.mChangedStart = Math.min(ims.mChangedStart, start);
7661                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
7662            }
7663            ims.mChangedDelta += after-before;
7664        }
7665
7666        sendOnTextChanged(buffer, start, before, after);
7667        onTextChanged(buffer, start, before, after);
7668    }
7669
7670    /**
7671     * Not private so it can be called from an inner class without going
7672     * through a thunk.
7673     */
7674    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
7675        // XXX Make the start and end move together if this ends up
7676        // spending too much time invalidating.
7677
7678        boolean selChanged = false;
7679        int newSelStart=-1, newSelEnd=-1;
7680
7681        final InputMethodState ims = mInputMethodState;
7682
7683        if (what == Selection.SELECTION_END) {
7684            mHighlightPathBogus = true;
7685            selChanged = true;
7686            newSelEnd = newStart;
7687
7688            if (!isFocused()) {
7689                mSelectionMoved = true;
7690            }
7691
7692            if (oldStart >= 0 || newStart >= 0) {
7693                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
7694                registerForPreDraw();
7695                makeBlink();
7696            }
7697        }
7698
7699        if (what == Selection.SELECTION_START) {
7700            mHighlightPathBogus = true;
7701            selChanged = true;
7702            newSelStart = newStart;
7703
7704            if (!isFocused()) {
7705                mSelectionMoved = true;
7706            }
7707
7708            if (oldStart >= 0 || newStart >= 0) {
7709                int end = Selection.getSelectionEnd(buf);
7710                invalidateCursor(end, oldStart, newStart);
7711            }
7712        }
7713
7714        if (selChanged) {
7715            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7716                if (newSelStart < 0) {
7717                    newSelStart = Selection.getSelectionStart(buf);
7718                }
7719                if (newSelEnd < 0) {
7720                    newSelEnd = Selection.getSelectionEnd(buf);
7721                }
7722                onSelectionChanged(newSelStart, newSelEnd);
7723            }
7724        }
7725
7726        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7727                what instanceof CharacterStyle) {
7728            if (ims == null || ims.mBatchEditNesting == 0) {
7729                invalidate();
7730                mHighlightPathBogus = true;
7731                checkForResize();
7732            } else {
7733                ims.mContentChanged = true;
7734            }
7735            mTextDisplayListIsValid = false;
7736        }
7737
7738        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7739            mHighlightPathBogus = true;
7740            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7741                ims.mSelectionModeChanged = true;
7742            }
7743
7744            if (Selection.getSelectionStart(buf) >= 0) {
7745                if (ims == null || ims.mBatchEditNesting == 0) {
7746                    invalidateCursor();
7747                } else {
7748                    ims.mCursorChanged = true;
7749                }
7750            }
7751        }
7752
7753        if (what instanceof ParcelableSpan) {
7754            // If this is a span that can be sent to a remote process,
7755            // the current extract editor would be interested in it.
7756            if (ims != null && ims.mExtracting != null) {
7757                if (ims.mBatchEditNesting != 0) {
7758                    if (oldStart >= 0) {
7759                        if (ims.mChangedStart > oldStart) {
7760                            ims.mChangedStart = oldStart;
7761                        }
7762                        if (ims.mChangedStart > oldEnd) {
7763                            ims.mChangedStart = oldEnd;
7764                        }
7765                    }
7766                    if (newStart >= 0) {
7767                        if (ims.mChangedStart > newStart) {
7768                            ims.mChangedStart = newStart;
7769                        }
7770                        if (ims.mChangedStart > newEnd) {
7771                            ims.mChangedStart = newEnd;
7772                        }
7773                    }
7774                } else {
7775                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7776                            + oldStart + "-" + oldEnd + ","
7777                            + newStart + "-" + newEnd + what);
7778                    ims.mContentChanged = true;
7779                }
7780            }
7781        }
7782
7783        if (mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
7784            mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
7785        }
7786    }
7787
7788    /**
7789     * Create new SpellCheckSpans on the modified region.
7790     */
7791    private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) {
7792        if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) {
7793            if (mSpellChecker == null && createSpellChecker) {
7794                mSpellChecker = new SpellChecker(this);
7795            }
7796            if (mSpellChecker != null) {
7797                mSpellChecker.spellCheck(start, end);
7798            }
7799        }
7800    }
7801
7802    /**
7803     * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
7804     * pop-up should be displayed.
7805     */
7806    private class EasyEditSpanController {
7807
7808        private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
7809
7810        private EasyEditPopupWindow mPopupWindow;
7811
7812        private EasyEditSpan mEasyEditSpan;
7813
7814        private Runnable mHidePopup;
7815
7816        private void hide() {
7817            if (mPopupWindow != null) {
7818                mPopupWindow.hide();
7819                TextView.this.removeCallbacks(mHidePopup);
7820            }
7821            removeSpans(mText);
7822            mEasyEditSpan = null;
7823        }
7824
7825        /**
7826         * Monitors the changes in the text.
7827         *
7828         * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
7829         * as the notifications are not sent when a spannable (with spans) is inserted.
7830         */
7831        public void onTextChange(CharSequence buffer) {
7832            adjustSpans(mText);
7833
7834            if (getWindowVisibility() != View.VISIBLE) {
7835                // The window is not visible yet, ignore the text change.
7836                return;
7837            }
7838
7839            if (mLayout == null) {
7840                // The view has not been layout yet, ignore the text change
7841                return;
7842            }
7843
7844            InputMethodManager imm = InputMethodManager.peekInstance();
7845            if (!(TextView.this instanceof ExtractEditText)
7846                    && imm != null && imm.isFullscreenMode()) {
7847                // The input is in extract mode. We do not have to handle the easy edit in the
7848                // original TextView, as the ExtractEditText will do
7849                return;
7850            }
7851
7852            // Remove the current easy edit span, as the text changed, and remove the pop-up
7853            // (if any)
7854            if (mEasyEditSpan != null) {
7855                if (mText instanceof Spannable) {
7856                    ((Spannable) mText).removeSpan(mEasyEditSpan);
7857                }
7858                mEasyEditSpan = null;
7859            }
7860            if (mPopupWindow != null && mPopupWindow.isShowing()) {
7861                mPopupWindow.hide();
7862            }
7863
7864            // Display the new easy edit span (if any).
7865            if (buffer instanceof Spanned) {
7866                mEasyEditSpan = getSpan((Spanned) buffer);
7867                if (mEasyEditSpan != null) {
7868                    if (mPopupWindow == null) {
7869                        mPopupWindow = new EasyEditPopupWindow();
7870                        mHidePopup = new Runnable() {
7871                            @Override
7872                            public void run() {
7873                                hide();
7874                            }
7875                        };
7876                    }
7877                    mPopupWindow.show(mEasyEditSpan);
7878                    TextView.this.removeCallbacks(mHidePopup);
7879                    TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
7880                }
7881            }
7882        }
7883
7884        /**
7885         * Adjusts the spans by removing all of them except the last one.
7886         */
7887        private void adjustSpans(CharSequence buffer) {
7888            // This method enforces that only one easy edit span is attached to the text.
7889            // A better way to enforce this would be to listen for onSpanAdded, but this method
7890            // cannot be used in this scenario as no notification is triggered when a text with
7891            // spans is inserted into a text.
7892            if (buffer instanceof Spannable) {
7893                Spannable spannable = (Spannable) buffer;
7894                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
7895                        EasyEditSpan.class);
7896                for (int i = 0; i < spans.length - 1; i++) {
7897                    spannable.removeSpan(spans[i]);
7898                }
7899            }
7900        }
7901
7902        /**
7903         * Removes all the {@link EasyEditSpan} currently attached.
7904         */
7905        private void removeSpans(CharSequence buffer) {
7906            if (buffer instanceof Spannable) {
7907                Spannable spannable = (Spannable) buffer;
7908                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
7909                        EasyEditSpan.class);
7910                for (int i = 0; i < spans.length; i++) {
7911                    spannable.removeSpan(spans[i]);
7912                }
7913            }
7914        }
7915
7916        private EasyEditSpan getSpan(Spanned spanned) {
7917            EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
7918                    EasyEditSpan.class);
7919            if (easyEditSpans.length == 0) {
7920                return null;
7921            } else {
7922                return easyEditSpans[0];
7923            }
7924        }
7925    }
7926
7927    /**
7928     * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
7929     * by {@link EasyEditSpanController}.
7930     */
7931    private class EasyEditPopupWindow extends PinnedPopupWindow
7932            implements OnClickListener {
7933        private static final int POPUP_TEXT_LAYOUT =
7934                com.android.internal.R.layout.text_edit_action_popup_text;
7935        private TextView mDeleteTextView;
7936        private EasyEditSpan mEasyEditSpan;
7937
7938        @Override
7939        protected void createPopupWindow() {
7940            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
7941                    com.android.internal.R.attr.textSelectHandleWindowStyle);
7942            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
7943            mPopupWindow.setClippingEnabled(true);
7944        }
7945
7946        @Override
7947        protected void initContentView() {
7948            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
7949            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
7950            mContentView = linearLayout;
7951            mContentView.setBackgroundResource(
7952                    com.android.internal.R.drawable.text_edit_side_paste_window);
7953
7954            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
7955                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
7956
7957            LayoutParams wrapContent = new LayoutParams(
7958                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
7959
7960            mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
7961            mDeleteTextView.setLayoutParams(wrapContent);
7962            mDeleteTextView.setText(com.android.internal.R.string.delete);
7963            mDeleteTextView.setOnClickListener(this);
7964            mContentView.addView(mDeleteTextView);
7965        }
7966
7967        public void show(EasyEditSpan easyEditSpan) {
7968            mEasyEditSpan = easyEditSpan;
7969            super.show();
7970        }
7971
7972        @Override
7973        public void onClick(View view) {
7974            if (view == mDeleteTextView) {
7975                Editable editable = (Editable) mText;
7976                int start = editable.getSpanStart(mEasyEditSpan);
7977                int end = editable.getSpanEnd(mEasyEditSpan);
7978                if (start >= 0 && end >= 0) {
7979                    deleteText_internal(start, end);
7980                }
7981            }
7982        }
7983
7984        @Override
7985        protected int getTextOffset() {
7986            // Place the pop-up at the end of the span
7987            Editable editable = (Editable) mText;
7988            return editable.getSpanEnd(mEasyEditSpan);
7989        }
7990
7991        @Override
7992        protected int getVerticalLocalPosition(int line) {
7993            return mLayout.getLineBottom(line);
7994        }
7995
7996        @Override
7997        protected int clipVertically(int positionY) {
7998            // As we display the pop-up below the span, no vertical clipping is required.
7999            return positionY;
8000        }
8001    }
8002
8003    private class ChangeWatcher implements TextWatcher, SpanWatcher {
8004
8005        private CharSequence mBeforeText;
8006
8007        private EasyEditSpanController mEasyEditSpanController;
8008
8009        private ChangeWatcher() {
8010            mEasyEditSpanController = new EasyEditSpanController();
8011        }
8012
8013        public void beforeTextChanged(CharSequence buffer, int start,
8014                                      int before, int after) {
8015            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
8016                    + " before=" + before + " after=" + after + ": " + buffer);
8017
8018            if (AccessibilityManager.getInstance(mContext).isEnabled()
8019                    && !isPasswordInputType(mInputType)
8020                    && !hasPasswordTransformationMethod()) {
8021                mBeforeText = buffer.toString();
8022            }
8023
8024            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
8025        }
8026
8027        public void onTextChanged(CharSequence buffer, int start,
8028                                  int before, int after) {
8029            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
8030                    + " before=" + before + " after=" + after + ": " + buffer);
8031            TextView.this.handleTextChanged(buffer, start, before, after);
8032
8033            mEasyEditSpanController.onTextChange(buffer);
8034
8035            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
8036                    (isFocused() || isSelected() && isShown())) {
8037                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
8038                mBeforeText = null;
8039            }
8040        }
8041
8042        public void afterTextChanged(Editable buffer) {
8043            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
8044            TextView.this.sendAfterTextChanged(buffer);
8045
8046            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
8047                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
8048            }
8049        }
8050
8051        public void onSpanChanged(Spannable buf,
8052                                  Object what, int s, int e, int st, int en) {
8053            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
8054                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
8055            TextView.this.spanChange(buf, what, s, st, e, en);
8056        }
8057
8058        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
8059            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
8060                    + " what=" + what + ": " + buf);
8061            TextView.this.spanChange(buf, what, -1, s, -1, e);
8062        }
8063
8064        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
8065            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
8066                    + " what=" + what + ": " + buf);
8067            TextView.this.spanChange(buf, what, s, -1, e, -1);
8068        }
8069
8070        private void hideControllers() {
8071            mEasyEditSpanController.hide();
8072        }
8073    }
8074
8075    /**
8076     * @hide
8077     */
8078    @Override
8079    public void dispatchFinishTemporaryDetach() {
8080        mDispatchTemporaryDetach = true;
8081        super.dispatchFinishTemporaryDetach();
8082        mDispatchTemporaryDetach = false;
8083    }
8084
8085    @Override
8086    public void onStartTemporaryDetach() {
8087        super.onStartTemporaryDetach();
8088        // Only track when onStartTemporaryDetach() is called directly,
8089        // usually because this instance is an editable field in a list
8090        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
8091
8092        // Because of View recycling in ListView, there is no easy way to know when a TextView with
8093        // selection becomes visible again. Until a better solution is found, stop text selection
8094        // mode (if any) as soon as this TextView is recycled.
8095        hideControllers();
8096    }
8097
8098    @Override
8099    public void onFinishTemporaryDetach() {
8100        super.onFinishTemporaryDetach();
8101        // Only track when onStartTemporaryDetach() is called directly,
8102        // usually because this instance is an editable field in a list
8103        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
8104    }
8105
8106    @Override
8107    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8108        if (mTemporaryDetach) {
8109            // If we are temporarily in the detach state, then do nothing.
8110            super.onFocusChanged(focused, direction, previouslyFocusedRect);
8111            return;
8112        }
8113
8114        mShowCursor = SystemClock.uptimeMillis();
8115
8116        ensureEndedBatchEdit();
8117
8118        if (focused) {
8119            int selStart = getSelectionStart();
8120            int selEnd = getSelectionEnd();
8121
8122            // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
8123            // mode for these, unless there was a specific selection already started.
8124            final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
8125                    selEnd == mText.length();
8126            mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
8127
8128            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
8129                // If a tap was used to give focus to that view, move cursor at tap position.
8130                // Has to be done before onTakeFocus, which can be overloaded.
8131                final int lastTapPosition = getLastTapPosition();
8132                if (lastTapPosition >= 0) {
8133                    Selection.setSelection((Spannable) mText, lastTapPosition);
8134                }
8135
8136                if (mMovement != null) {
8137                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
8138                }
8139
8140                // The DecorView does not have focus when the 'Done' ExtractEditText button is
8141                // pressed. Since it is the ViewAncestor's mView, it requests focus before
8142                // ExtractEditText clears focus, which gives focus to the ExtractEditText.
8143                // This special case ensure that we keep current selection in that case.
8144                // It would be better to know why the DecorView does not have focus at that time.
8145                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
8146                        selStart >= 0 && selEnd >= 0) {
8147                    /*
8148                     * Someone intentionally set the selection, so let them
8149                     * do whatever it is that they wanted to do instead of
8150                     * the default on-focus behavior.  We reset the selection
8151                     * here instead of just skipping the onTakeFocus() call
8152                     * because some movement methods do something other than
8153                     * just setting the selection in theirs and we still
8154                     * need to go through that path.
8155                     */
8156                    Selection.setSelection((Spannable) mText, selStart, selEnd);
8157                }
8158
8159                if (mSelectAllOnFocus) {
8160                    selectAll();
8161                }
8162
8163                mTouchFocusSelected = true;
8164            }
8165
8166            mFrozenWithFocus = false;
8167            mSelectionMoved = false;
8168
8169            if (mText instanceof Spannable) {
8170                Spannable sp = (Spannable) mText;
8171                MetaKeyKeyListener.resetMetaState(sp);
8172            }
8173
8174            makeBlink();
8175
8176            if (mError != null) {
8177                showError();
8178            }
8179        } else {
8180            if (mError != null) {
8181                hideError();
8182            }
8183            // Don't leave us in the middle of a batch edit.
8184            onEndBatchEdit();
8185
8186            if (this instanceof ExtractEditText) {
8187                // terminateTextSelectionMode removes selection, which we want to keep when
8188                // ExtractEditText goes out of focus.
8189                final int selStart = getSelectionStart();
8190                final int selEnd = getSelectionEnd();
8191                hideControllers();
8192                Selection.setSelection((Spannable) mText, selStart, selEnd);
8193            } else {
8194                hideControllers();
8195                downgradeEasyCorrectionSpans();
8196            }
8197
8198            // No need to create the controller
8199            if (mSelectionModifierCursorController != null) {
8200                mSelectionModifierCursorController.resetTouchOffsets();
8201            }
8202        }
8203
8204        startStopMarquee(focused);
8205
8206        if (mTransformation != null) {
8207            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8208        }
8209
8210        super.onFocusChanged(focused, direction, previouslyFocusedRect);
8211    }
8212
8213    private int getLastTapPosition() {
8214        // No need to create the controller at that point, no last tap position saved
8215        if (mSelectionModifierCursorController != null) {
8216            int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
8217            if (lastTapPosition >= 0) {
8218                // Safety check, should not be possible.
8219                if (lastTapPosition > mText.length()) {
8220                    Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
8221                            + mText.length() + ")");
8222                    lastTapPosition = mText.length();
8223                }
8224                return lastTapPosition;
8225            }
8226        }
8227
8228        return -1;
8229    }
8230
8231    @Override
8232    public void onWindowFocusChanged(boolean hasWindowFocus) {
8233        super.onWindowFocusChanged(hasWindowFocus);
8234
8235        if (hasWindowFocus) {
8236            if (mBlink != null) {
8237                mBlink.uncancel();
8238                makeBlink();
8239            }
8240        } else {
8241            if (mBlink != null) {
8242                mBlink.cancel();
8243            }
8244            // Don't leave us in the middle of a batch edit.
8245            onEndBatchEdit();
8246            if (mInputContentType != null) {
8247                mInputContentType.enterDown = false;
8248            }
8249
8250            hideControllers();
8251            if (mSuggestionsPopupWindow != null) {
8252                mSuggestionsPopupWindow.onParentLostFocus();
8253            }
8254        }
8255
8256        startStopMarquee(hasWindowFocus);
8257    }
8258
8259    @Override
8260    protected void onVisibilityChanged(View changedView, int visibility) {
8261        super.onVisibilityChanged(changedView, visibility);
8262        if (visibility != VISIBLE) {
8263            hideControllers();
8264        }
8265    }
8266
8267    /**
8268     * Use {@link BaseInputConnection#removeComposingSpans
8269     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8270     * state from this text view.
8271     */
8272    public void clearComposingText() {
8273        if (mText instanceof Spannable) {
8274            BaseInputConnection.removeComposingSpans((Spannable)mText);
8275        }
8276    }
8277
8278    @Override
8279    public void setSelected(boolean selected) {
8280        boolean wasSelected = isSelected();
8281
8282        super.setSelected(selected);
8283
8284        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8285            if (selected) {
8286                startMarquee();
8287            } else {
8288                stopMarquee();
8289            }
8290        }
8291    }
8292
8293    @Override
8294    public boolean onTouchEvent(MotionEvent event) {
8295        final int action = event.getActionMasked();
8296
8297        if (hasSelectionController()) {
8298            getSelectionController().onTouchEvent(event);
8299        }
8300
8301        if (mShowSuggestionRunnable != null) {
8302            removeCallbacks(mShowSuggestionRunnable);
8303        }
8304
8305        if (action == MotionEvent.ACTION_DOWN) {
8306            mLastDownPositionX = event.getX();
8307            mLastDownPositionY = event.getY();
8308
8309            // Reset this state; it will be re-set if super.onTouchEvent
8310            // causes focus to move to the view.
8311            mTouchFocusSelected = false;
8312            mIgnoreActionUpEvent = false;
8313        }
8314
8315        final boolean superResult = super.onTouchEvent(event);
8316
8317        /*
8318         * Don't handle the release after a long press, because it will
8319         * move the selection away from whatever the menu action was
8320         * trying to affect.
8321         */
8322        if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8323            mDiscardNextActionUp = false;
8324            return superResult;
8325        }
8326
8327        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8328                !mIgnoreActionUpEvent && isFocused();
8329
8330         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8331                && mText instanceof Spannable && mLayout != null) {
8332            boolean handled = false;
8333
8334            if (mMovement != null) {
8335                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8336            }
8337
8338            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable) {
8339                // The LinkMovementMethod which should handle taps on links has not been installed
8340                // on non editable text that support text selection.
8341                // We reproduce its behavior here to open links for these.
8342                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8343                        getSelectionEnd(), ClickableSpan.class);
8344
8345                if (links.length > 0) {
8346                    links[0].onClick(this);
8347                    handled = true;
8348                }
8349            }
8350
8351            if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
8352                // Show the IME, except when selecting in read-only text.
8353                final InputMethodManager imm = InputMethodManager.peekInstance();
8354                viewClicked(imm);
8355                if (!mTextIsSelectable) {
8356                    handled |= imm != null && imm.showSoftInput(this, 0);
8357                }
8358
8359                boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
8360                hideControllers();
8361                if (!selectAllGotFocus && mText.length() > 0) {
8362                    // Move cursor
8363                    final int offset = getOffsetForPosition(event.getX(), event.getY());
8364                    Selection.setSelection((Spannable) mText, offset);
8365                    if (mSpellChecker != null) {
8366                        // When the cursor moves, the word that was typed may need spell check
8367                        mSpellChecker.onSelectionChanged();
8368                    }
8369                    if (!extractedTextModeWillBeStarted()) {
8370                        if (isCursorInsideEasyCorrectionSpan()) {
8371                            if (mShowSuggestionRunnable == null) {
8372                                mShowSuggestionRunnable = new Runnable() {
8373                                    public void run() {
8374                                        showSuggestions();
8375                                    }
8376                                };
8377                            }
8378                            postDelayed(mShowSuggestionRunnable,
8379                                    ViewConfiguration.getDoubleTapTimeout());
8380                        } else if (hasInsertionController()) {
8381                            getInsertionController().show();
8382                        }
8383                    }
8384                }
8385
8386                handled = true;
8387            }
8388
8389            if (handled) {
8390                return true;
8391            }
8392        }
8393
8394        return superResult;
8395    }
8396
8397    /**
8398     * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}.
8399     */
8400    private boolean isCursorInsideSuggestionSpan() {
8401        if (!(mText instanceof Spannable)) return false;
8402
8403        SuggestionSpan[] suggestionSpans = ((Spannable) mText).getSpans(getSelectionStart(),
8404                getSelectionEnd(), SuggestionSpan.class);
8405        return (suggestionSpans.length > 0);
8406    }
8407
8408    /**
8409     * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with
8410     * {@link SuggestionSpan#FLAG_EASY_CORRECT} set.
8411     */
8412    private boolean isCursorInsideEasyCorrectionSpan() {
8413        Spannable spannable = (Spannable) mText;
8414        SuggestionSpan[] suggestionSpans = spannable.getSpans(getSelectionStart(),
8415                getSelectionEnd(), SuggestionSpan.class);
8416        for (int i = 0; i < suggestionSpans.length; i++) {
8417            if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) {
8418                return true;
8419            }
8420        }
8421        return false;
8422    }
8423
8424    /**
8425     * Downgrades to simple suggestions all the easy correction spans that are not a spell check
8426     * span.
8427     */
8428    private void downgradeEasyCorrectionSpans() {
8429        if (mText instanceof Spannable) {
8430            Spannable spannable = (Spannable) mText;
8431            SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
8432                    spannable.length(), SuggestionSpan.class);
8433            for (int i = 0; i < suggestionSpans.length; i++) {
8434                int flags = suggestionSpans[i].getFlags();
8435                if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
8436                        && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) {
8437                    flags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
8438                    suggestionSpans[i].setFlags(flags);
8439                }
8440            }
8441        }
8442    }
8443
8444    @Override
8445    public boolean onGenericMotionEvent(MotionEvent event) {
8446        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8447            try {
8448                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8449                    return true;
8450                }
8451            } catch (AbstractMethodError ex) {
8452                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8453                // Ignore its absence in case third party applications implemented the
8454                // interface directly.
8455            }
8456        }
8457        return super.onGenericMotionEvent(event);
8458    }
8459
8460    private void prepareCursorControllers() {
8461        boolean windowSupportsHandles = false;
8462
8463        ViewGroup.LayoutParams params = getRootView().getLayoutParams();
8464        if (params instanceof WindowManager.LayoutParams) {
8465            WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
8466            windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
8467                    || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
8468        }
8469
8470        mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
8471        mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
8472                mLayout != null;
8473
8474        if (!mInsertionControllerEnabled) {
8475            hideInsertionPointCursorController();
8476            if (mInsertionPointCursorController != null) {
8477                mInsertionPointCursorController.onDetached();
8478                mInsertionPointCursorController = null;
8479            }
8480        }
8481
8482        if (!mSelectionControllerEnabled) {
8483            stopSelectionActionMode();
8484            if (mSelectionModifierCursorController != null) {
8485                mSelectionModifierCursorController.onDetached();
8486                mSelectionModifierCursorController = null;
8487            }
8488        }
8489    }
8490
8491    /**
8492     * @return True iff this TextView contains a text that can be edited, or if this is
8493     * a selectable TextView.
8494     */
8495    private boolean isTextEditable() {
8496        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8497    }
8498
8499    /**
8500     * Returns true, only while processing a touch gesture, if the initial
8501     * touch down event caused focus to move to the text view and as a result
8502     * its selection changed.  Only valid while processing the touch gesture
8503     * of interest.
8504     */
8505    public boolean didTouchFocusSelect() {
8506        return mTouchFocusSelected;
8507    }
8508
8509    @Override
8510    public void cancelLongPress() {
8511        super.cancelLongPress();
8512        mIgnoreActionUpEvent = true;
8513    }
8514
8515    @Override
8516    public boolean onTrackballEvent(MotionEvent event) {
8517        if (mMovement != null && mText instanceof Spannable &&
8518            mLayout != null) {
8519            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8520                return true;
8521            }
8522        }
8523
8524        return super.onTrackballEvent(event);
8525    }
8526
8527    public void setScroller(Scroller s) {
8528        mScroller = s;
8529    }
8530
8531    private static class Blink extends Handler implements Runnable {
8532        private final WeakReference<TextView> mView;
8533        private boolean mCancelled;
8534
8535        public Blink(TextView v) {
8536            mView = new WeakReference<TextView>(v);
8537        }
8538
8539        public void run() {
8540            if (mCancelled) {
8541                return;
8542            }
8543
8544            removeCallbacks(Blink.this);
8545
8546            TextView tv = mView.get();
8547
8548            if (tv != null && tv.shouldBlink()) {
8549                if (tv.mLayout != null) {
8550                    tv.invalidateCursorPath();
8551                }
8552
8553                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
8554            }
8555        }
8556
8557        void cancel() {
8558            if (!mCancelled) {
8559                removeCallbacks(Blink.this);
8560                mCancelled = true;
8561            }
8562        }
8563
8564        void uncancel() {
8565            mCancelled = false;
8566        }
8567    }
8568
8569    /**
8570     * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
8571     */
8572    private boolean shouldBlink() {
8573        if (!isCursorVisible() || !isFocused()) return false;
8574
8575        final int start = getSelectionStart();
8576        if (start < 0) return false;
8577
8578        final int end = getSelectionEnd();
8579        if (end < 0) return false;
8580
8581        return start == end;
8582    }
8583
8584    private void makeBlink() {
8585        if (shouldBlink()) {
8586            mShowCursor = SystemClock.uptimeMillis();
8587            if (mBlink == null) mBlink = new Blink(this);
8588            mBlink.removeCallbacks(mBlink);
8589            mBlink.postAtTime(mBlink, mShowCursor + BLINK);
8590        } else {
8591            if (mBlink != null) mBlink.removeCallbacks(mBlink);
8592        }
8593    }
8594
8595    @Override
8596    protected float getLeftFadingEdgeStrength() {
8597        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
8598        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8599                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8600            if (mMarquee != null && !mMarquee.isStopped()) {
8601                final Marquee marquee = mMarquee;
8602                if (marquee.shouldDrawLeftFade()) {
8603                    return marquee.mScroll / getHorizontalFadingEdgeLength();
8604                } else {
8605                    return 0.0f;
8606                }
8607            } else if (getLineCount() == 1) {
8608                final int layoutDirection = getResolvedLayoutDirection();
8609                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8610                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8611                    case Gravity.LEFT:
8612                        return 0.0f;
8613                    case Gravity.RIGHT:
8614                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
8615                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
8616                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8617                    case Gravity.CENTER_HORIZONTAL:
8618                        return 0.0f;
8619                }
8620            }
8621        }
8622        return super.getLeftFadingEdgeStrength();
8623    }
8624
8625    @Override
8626    protected float getRightFadingEdgeStrength() {
8627        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
8628        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8629                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8630            if (mMarquee != null && !mMarquee.isStopped()) {
8631                final Marquee marquee = mMarquee;
8632                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
8633            } else if (getLineCount() == 1) {
8634                final int layoutDirection = getResolvedLayoutDirection();
8635                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8636                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8637                    case Gravity.LEFT:
8638                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8639                                getCompoundPaddingRight();
8640                        final float lineWidth = mLayout.getLineWidth(0);
8641                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8642                    case Gravity.RIGHT:
8643                        return 0.0f;
8644                    case Gravity.CENTER_HORIZONTAL:
8645                    case Gravity.FILL_HORIZONTAL:
8646                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8647                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8648                                getHorizontalFadingEdgeLength();
8649                }
8650            }
8651        }
8652        return super.getRightFadingEdgeStrength();
8653    }
8654
8655    @Override
8656    protected int computeHorizontalScrollRange() {
8657        if (mLayout != null) {
8658            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8659                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8660        }
8661
8662        return super.computeHorizontalScrollRange();
8663    }
8664
8665    @Override
8666    protected int computeVerticalScrollRange() {
8667        if (mLayout != null)
8668            return mLayout.getHeight();
8669
8670        return super.computeVerticalScrollRange();
8671    }
8672
8673    @Override
8674    protected int computeVerticalScrollExtent() {
8675        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8676    }
8677
8678    @Override
8679    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8680        super.findViewsWithText(outViews, searched, flags);
8681        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8682                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8683            String searchedLowerCase = searched.toString().toLowerCase();
8684            String textLowerCase = mText.toString().toLowerCase();
8685            if (textLowerCase.contains(searchedLowerCase)) {
8686                outViews.add(this);
8687            }
8688        }
8689    }
8690
8691    public enum BufferType {
8692        NORMAL, SPANNABLE, EDITABLE,
8693    }
8694
8695    /**
8696     * Returns the TextView_textColor attribute from the
8697     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
8698     * from the TextView_textAppearance attribute, if TextView_textColor
8699     * was not set directly.
8700     */
8701    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8702        ColorStateList colors;
8703        colors = attrs.getColorStateList(com.android.internal.R.styleable.
8704                                         TextView_textColor);
8705
8706        if (colors == null) {
8707            int ap = attrs.getResourceId(com.android.internal.R.styleable.
8708                                         TextView_textAppearance, -1);
8709            if (ap != -1) {
8710                TypedArray appearance;
8711                appearance = context.obtainStyledAttributes(ap,
8712                                            com.android.internal.R.styleable.TextAppearance);
8713                colors = appearance.getColorStateList(com.android.internal.R.styleable.
8714                                                  TextAppearance_textColor);
8715                appearance.recycle();
8716            }
8717        }
8718
8719        return colors;
8720    }
8721
8722    /**
8723     * Returns the default color from the TextView_textColor attribute
8724     * from the AttributeSet, if set, or the default color from the
8725     * TextAppearance_textColor from the TextView_textAppearance attribute,
8726     * if TextView_textColor was not set directly.
8727     */
8728    public static int getTextColor(Context context,
8729                                   TypedArray attrs,
8730                                   int def) {
8731        ColorStateList colors = getTextColors(context, attrs);
8732
8733        if (colors == null) {
8734            return def;
8735        } else {
8736            return colors.getDefaultColor();
8737        }
8738    }
8739
8740    @Override
8741    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8742        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
8743        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
8744            switch (keyCode) {
8745            case KeyEvent.KEYCODE_A:
8746                if (canSelectText()) {
8747                    return onTextContextMenuItem(ID_SELECT_ALL);
8748                }
8749                break;
8750            case KeyEvent.KEYCODE_X:
8751                if (canCut()) {
8752                    return onTextContextMenuItem(ID_CUT);
8753                }
8754                break;
8755            case KeyEvent.KEYCODE_C:
8756                if (canCopy()) {
8757                    return onTextContextMenuItem(ID_COPY);
8758                }
8759                break;
8760            case KeyEvent.KEYCODE_V:
8761                if (canPaste()) {
8762                    return onTextContextMenuItem(ID_PASTE);
8763                }
8764                break;
8765            }
8766        }
8767        return super.onKeyShortcut(keyCode, event);
8768    }
8769
8770    /**
8771     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8772     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8773     * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient.
8774     */
8775    private boolean canSelectText() {
8776        return hasSelectionController() && mText.length() != 0;
8777    }
8778
8779    /**
8780     * Test based on the <i>intrinsic</i> charateristics of the TextView.
8781     * The text must be spannable and the movement method must allow for arbitary selection.
8782     *
8783     * See also {@link #canSelectText()}.
8784     */
8785    private boolean textCanBeSelected() {
8786        // prepareCursorController() relies on this method.
8787        // If you change this condition, make sure prepareCursorController is called anywhere
8788        // the value of this condition might be changed.
8789        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8790        return isTextEditable() || (mTextIsSelectable && mText instanceof Spannable && isEnabled());
8791    }
8792
8793    private boolean canCut() {
8794        if (hasPasswordTransformationMethod()) {
8795            return false;
8796        }
8797
8798        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
8799            return true;
8800        }
8801
8802        return false;
8803    }
8804
8805    private boolean canCopy() {
8806        if (hasPasswordTransformationMethod()) {
8807            return false;
8808        }
8809
8810        if (mText.length() > 0 && hasSelection()) {
8811            return true;
8812        }
8813
8814        return false;
8815    }
8816
8817    private boolean canPaste() {
8818        return (mText instanceof Editable &&
8819                mInput != null &&
8820                getSelectionStart() >= 0 &&
8821                getSelectionEnd() >= 0 &&
8822                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8823                hasPrimaryClip());
8824    }
8825
8826    private static long packRangeInLong(int start, int end) {
8827        return (((long) start) << 32) | end;
8828    }
8829
8830    private static int extractRangeStartFromLong(long range) {
8831        return (int) (range >>> 32);
8832    }
8833
8834    private static int extractRangeEndFromLong(long range) {
8835        return (int) (range & 0x00000000FFFFFFFFL);
8836    }
8837
8838    private boolean selectAll() {
8839        final int length = mText.length();
8840        Selection.setSelection((Spannable) mText, 0, length);
8841        return length > 0;
8842    }
8843
8844    /**
8845     * Adjusts selection to the word under last touch offset.
8846     * Return true if the operation was successfully performed.
8847     */
8848    private boolean selectCurrentWord() {
8849        if (!canSelectText()) {
8850            return false;
8851        }
8852
8853        if (hasPasswordTransformationMethod()) {
8854            // Always select all on a password field.
8855            // Cut/copy menu entries are not available for passwords, but being able to select all
8856            // is however useful to delete or paste to replace the entire content.
8857            return selectAll();
8858        }
8859
8860        int klass = mInputType & InputType.TYPE_MASK_CLASS;
8861        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
8862
8863        // Specific text field types: select the entire text for these
8864        if (klass == InputType.TYPE_CLASS_NUMBER ||
8865                klass == InputType.TYPE_CLASS_PHONE ||
8866                klass == InputType.TYPE_CLASS_DATETIME ||
8867                variation == InputType.TYPE_TEXT_VARIATION_URI ||
8868                variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
8869                variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
8870                variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
8871            return selectAll();
8872        }
8873
8874        long lastTouchOffsets = getLastTouchOffsets();
8875        final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
8876        final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
8877
8878        // Safety check in case standard touch event handling has been bypassed
8879        if (minOffset < 0 || minOffset >= mText.length()) return false;
8880        if (maxOffset < 0 || maxOffset >= mText.length()) return false;
8881
8882        int selectionStart, selectionEnd;
8883
8884        // If a URLSpan (web address, email, phone...) is found at that position, select it.
8885        URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
8886        if (urlSpans.length >= 1) {
8887            URLSpan urlSpan = urlSpans[0];
8888            selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
8889            selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
8890        } else {
8891            final WordIterator wordIterator = getWordIterator();
8892            wordIterator.setCharSequence(mText, minOffset, maxOffset);
8893
8894            selectionStart = wordIterator.getBeginning(minOffset);
8895            selectionEnd = wordIterator.getEnd(maxOffset);
8896
8897            if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE ||
8898                    selectionStart == selectionEnd) {
8899                // Possible when the word iterator does not properly handle the text's language
8900                long range = getCharRange(minOffset);
8901                selectionStart = extractRangeStartFromLong(range);
8902                selectionEnd = extractRangeEndFromLong(range);
8903            }
8904        }
8905
8906        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8907        return selectionEnd > selectionStart;
8908    }
8909
8910    /**
8911     * This is a temporary method. Future versions may support multi-locale text.
8912     *
8913     * @return The locale that should be used for a word iterator and a spell checker
8914     * in this TextView, based on the current spell checker settings,
8915     * the current IME's locale, or the system default locale.
8916     * @hide
8917     */
8918    public Locale getTextServicesLocale() {
8919        Locale locale = Locale.getDefault();
8920        final TextServicesManager textServicesManager = (TextServicesManager)
8921                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8922        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8923        if (subtype != null) {
8924            locale = new Locale(subtype.getLocale());
8925        }
8926        return locale;
8927    }
8928
8929    void onLocaleChanged() {
8930        // Will be re-created on demand in getWordIterator with the proper new locale
8931        mWordIterator = null;
8932    }
8933
8934    /**
8935     * @hide
8936     */
8937    public WordIterator getWordIterator() {
8938        if (mWordIterator == null) {
8939            mWordIterator = new WordIterator(getTextServicesLocale());
8940        }
8941        return mWordIterator;
8942    }
8943
8944    private long getCharRange(int offset) {
8945        final int textLength = mText.length();
8946        if (offset + 1 < textLength) {
8947            final char currentChar = mText.charAt(offset);
8948            final char nextChar = mText.charAt(offset + 1);
8949            if (Character.isSurrogatePair(currentChar, nextChar)) {
8950                return packRangeInLong(offset,  offset + 2);
8951            }
8952        }
8953        if (offset < textLength) {
8954            return packRangeInLong(offset,  offset + 1);
8955        }
8956        if (offset - 2 >= 0) {
8957            final char previousChar = mText.charAt(offset - 1);
8958            final char previousPreviousChar = mText.charAt(offset - 2);
8959            if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
8960                return packRangeInLong(offset - 2,  offset);
8961            }
8962        }
8963        if (offset - 1 >= 0) {
8964            return packRangeInLong(offset - 1,  offset);
8965        }
8966        return packRangeInLong(offset,  offset);
8967    }
8968
8969    private long getLastTouchOffsets() {
8970        SelectionModifierCursorController selectionController = getSelectionController();
8971        final int minOffset = selectionController.getMinTouchOffset();
8972        final int maxOffset = selectionController.getMaxTouchOffset();
8973        return packRangeInLong(minOffset, maxOffset);
8974    }
8975
8976    @Override
8977    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
8978        super.onPopulateAccessibilityEvent(event);
8979
8980        final boolean isPassword = hasPasswordTransformationMethod();
8981        if (!isPassword) {
8982            CharSequence text = getTextForAccessibility();
8983            if (!TextUtils.isEmpty(text)) {
8984                event.getText().add(text);
8985            }
8986        }
8987    }
8988
8989    @Override
8990    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8991        super.onInitializeAccessibilityEvent(event);
8992
8993        event.setClassName(TextView.class.getName());
8994        final boolean isPassword = hasPasswordTransformationMethod();
8995        event.setPassword(isPassword);
8996
8997        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8998            event.setFromIndex(Selection.getSelectionStart(mText));
8999            event.setToIndex(Selection.getSelectionEnd(mText));
9000            event.setItemCount(mText.length());
9001        }
9002    }
9003
9004    @Override
9005    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
9006        super.onInitializeAccessibilityNodeInfo(info);
9007
9008        info.setClassName(TextView.class.getName());
9009        final boolean isPassword = hasPasswordTransformationMethod();
9010        info.setPassword(isPassword);
9011
9012        if (!isPassword) {
9013            info.setText(getTextForAccessibility());
9014        }
9015    }
9016
9017    @Override
9018    public void sendAccessibilityEvent(int eventType) {
9019        // Do not send scroll events since first they are not interesting for
9020        // accessibility and second such events a generated too frequently.
9021        // For details see the implementation of bringTextIntoView().
9022        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9023            return;
9024        }
9025        super.sendAccessibilityEvent(eventType);
9026    }
9027
9028    /**
9029     * Gets the text reported for accessibility purposes. It is the
9030     * text if not empty or the hint.
9031     *
9032     * @return The accessibility text.
9033     */
9034    private CharSequence getTextForAccessibility() {
9035        CharSequence text = getText();
9036        if (TextUtils.isEmpty(text)) {
9037            text = getHint();
9038        }
9039        return text;
9040    }
9041
9042    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9043            int fromIndex, int removedCount, int addedCount) {
9044        AccessibilityEvent event =
9045            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9046        event.setFromIndex(fromIndex);
9047        event.setRemovedCount(removedCount);
9048        event.setAddedCount(addedCount);
9049        event.setBeforeText(beforeText);
9050        sendAccessibilityEventUnchecked(event);
9051    }
9052
9053    /**
9054     * Returns whether this text view is a current input method target.  The
9055     * default implementation just checks with {@link InputMethodManager}.
9056     */
9057    public boolean isInputMethodTarget() {
9058        InputMethodManager imm = InputMethodManager.peekInstance();
9059        return imm != null && imm.isActive(this);
9060    }
9061
9062    // Selection context mode
9063    private static final int ID_SELECT_ALL = android.R.id.selectAll;
9064    private static final int ID_CUT = android.R.id.cut;
9065    private static final int ID_COPY = android.R.id.copy;
9066    private static final int ID_PASTE = android.R.id.paste;
9067
9068    /**
9069     * Called when a context menu option for the text view is selected.  Currently
9070     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9071     * {@link android.R.id#copy} or {@link android.R.id#paste}.
9072     *
9073     * @return true if the context menu item action was performed.
9074     */
9075    public boolean onTextContextMenuItem(int id) {
9076        int min = 0;
9077        int max = mText.length();
9078
9079        if (isFocused()) {
9080            final int selStart = getSelectionStart();
9081            final int selEnd = getSelectionEnd();
9082
9083            min = Math.max(0, Math.min(selStart, selEnd));
9084            max = Math.max(0, Math.max(selStart, selEnd));
9085        }
9086
9087        switch (id) {
9088            case ID_SELECT_ALL:
9089                // This does not enter text selection mode. Text is highlighted, so that it can be
9090                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
9091                selectAll();
9092                return true;
9093
9094            case ID_PASTE:
9095                paste(min, max);
9096                return true;
9097
9098            case ID_CUT:
9099                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9100                deleteText_internal(min, max);
9101                stopSelectionActionMode();
9102                return true;
9103
9104            case ID_COPY:
9105                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9106                stopSelectionActionMode();
9107                return true;
9108        }
9109        return false;
9110    }
9111
9112    private CharSequence getTransformedText(int start, int end) {
9113        return removeSuggestionSpans(mTransformed.subSequence(start, end));
9114    }
9115
9116    /**
9117     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
9118     * by [min, max] when replacing this region by paste.
9119     * Note that if there were two spaces (or more) at that position before, they are kept. We just
9120     * make sure we do not add an extra one from the paste content.
9121     */
9122    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
9123        if (paste.length() > 0) {
9124            if (min > 0) {
9125                final char charBefore = mTransformed.charAt(min - 1);
9126                final char charAfter = paste.charAt(0);
9127
9128                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
9129                    // Two spaces at beginning of paste: remove one
9130                    final int originalLength = mText.length();
9131                    deleteText_internal(min - 1, min);
9132                    // Due to filters, there is no guarantee that exactly one character was
9133                    // removed: count instead.
9134                    final int delta = mText.length() - originalLength;
9135                    min += delta;
9136                    max += delta;
9137                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
9138                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
9139                    // No space at beginning of paste: add one
9140                    final int originalLength = mText.length();
9141                    replaceText_internal(min, min, " ");
9142                    // Taking possible filters into account as above.
9143                    final int delta = mText.length() - originalLength;
9144                    min += delta;
9145                    max += delta;
9146                }
9147            }
9148
9149            if (max < mText.length()) {
9150                final char charBefore = paste.charAt(paste.length() - 1);
9151                final char charAfter = mTransformed.charAt(max);
9152
9153                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
9154                    // Two spaces at end of paste: remove one
9155                    deleteText_internal(max, max + 1);
9156                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
9157                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
9158                    // No space at end of paste: add one
9159                    replaceText_internal(max, max, " ");
9160                }
9161            }
9162        }
9163
9164        return packRangeInLong(min, max);
9165    }
9166
9167    private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
9168        TextView shadowView = (TextView) inflate(mContext,
9169                com.android.internal.R.layout.text_drag_thumbnail, null);
9170
9171        if (shadowView == null) {
9172            throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
9173        }
9174
9175        if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
9176            text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
9177        }
9178        shadowView.setText(text);
9179        shadowView.setTextColor(getTextColors());
9180
9181        shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge);
9182        shadowView.setGravity(Gravity.CENTER);
9183
9184        shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
9185                ViewGroup.LayoutParams.WRAP_CONTENT));
9186
9187        final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
9188        shadowView.measure(size, size);
9189
9190        shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight());
9191        shadowView.invalidate();
9192        return new DragShadowBuilder(shadowView);
9193    }
9194
9195    private static class DragLocalState {
9196        public TextView sourceTextView;
9197        public int start, end;
9198
9199        public DragLocalState(TextView sourceTextView, int start, int end) {
9200            this.sourceTextView = sourceTextView;
9201            this.start = start;
9202            this.end = end;
9203        }
9204    }
9205
9206    @Override
9207    public boolean performLongClick() {
9208        boolean handled = false;
9209        boolean vibrate = true;
9210
9211        if (super.performLongClick()) {
9212            handled = true;
9213        }
9214
9215        // Long press in empty space moves cursor and shows the Paste affordance if available.
9216        if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
9217                mInsertionControllerEnabled) {
9218            final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
9219            stopSelectionActionMode();
9220            Selection.setSelection((Spannable) mText, offset);
9221            getInsertionController().showWithActionPopup();
9222            handled = true;
9223            vibrate = false;
9224        }
9225
9226        if (!handled && mSelectionActionMode != null) {
9227            if (touchPositionIsInSelection()) {
9228                // Start a drag
9229                final int start = getSelectionStart();
9230                final int end = getSelectionEnd();
9231                CharSequence selectedText = getTransformedText(start, end);
9232                ClipData data = ClipData.newPlainText(null, selectedText);
9233                DragLocalState localState = new DragLocalState(this, start, end);
9234                startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
9235                stopSelectionActionMode();
9236            } else {
9237                getSelectionController().hide();
9238                selectCurrentWord();
9239                getSelectionController().show();
9240            }
9241            handled = true;
9242        }
9243
9244        // Start a new selection
9245        if (!handled) {
9246            vibrate = handled = startSelectionActionMode();
9247        }
9248
9249        if (vibrate) {
9250            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9251        }
9252
9253        if (handled) {
9254            mDiscardNextActionUp = true;
9255        }
9256
9257        return handled;
9258    }
9259
9260    private boolean touchPositionIsInSelection() {
9261        int selectionStart = getSelectionStart();
9262        int selectionEnd = getSelectionEnd();
9263
9264        if (selectionStart == selectionEnd) {
9265            return false;
9266        }
9267
9268        if (selectionStart > selectionEnd) {
9269            int tmp = selectionStart;
9270            selectionStart = selectionEnd;
9271            selectionEnd = tmp;
9272            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
9273        }
9274
9275        SelectionModifierCursorController selectionController = getSelectionController();
9276        int minOffset = selectionController.getMinTouchOffset();
9277        int maxOffset = selectionController.getMaxTouchOffset();
9278
9279        return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
9280    }
9281
9282    private PositionListener getPositionListener() {
9283        if (mPositionListener == null) {
9284            mPositionListener = new PositionListener();
9285        }
9286        return mPositionListener;
9287    }
9288
9289    private interface TextViewPositionListener {
9290        public void updatePosition(int parentPositionX, int parentPositionY,
9291                boolean parentPositionChanged, boolean parentScrolled);
9292    }
9293
9294    private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
9295        // 3 handles
9296        // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
9297        private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
9298        private TextViewPositionListener[] mPositionListeners =
9299                new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
9300        private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
9301        private boolean mPositionHasChanged = true;
9302        // Absolute position of the TextView with respect to its parent window
9303        private int mPositionX, mPositionY;
9304        private int mNumberOfListeners;
9305        private boolean mScrollHasChanged;
9306
9307        public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
9308            if (mNumberOfListeners == 0) {
9309                updatePosition();
9310                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9311                vto.addOnPreDrawListener(this);
9312            }
9313
9314            int emptySlotIndex = -1;
9315            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9316                TextViewPositionListener listener = mPositionListeners[i];
9317                if (listener == positionListener) {
9318                    return;
9319                } else if (emptySlotIndex < 0 && listener == null) {
9320                    emptySlotIndex = i;
9321                }
9322            }
9323
9324            mPositionListeners[emptySlotIndex] = positionListener;
9325            mCanMove[emptySlotIndex] = canMove;
9326            mNumberOfListeners++;
9327        }
9328
9329        public void removeSubscriber(TextViewPositionListener positionListener) {
9330            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9331                if (mPositionListeners[i] == positionListener) {
9332                    mPositionListeners[i] = null;
9333                    mNumberOfListeners--;
9334                    break;
9335                }
9336            }
9337
9338            if (mNumberOfListeners == 0) {
9339                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9340                vto.removeOnPreDrawListener(this);
9341            }
9342        }
9343
9344        public int getPositionX() {
9345            return mPositionX;
9346        }
9347
9348        public int getPositionY() {
9349            return mPositionY;
9350        }
9351
9352        @Override
9353        public boolean onPreDraw() {
9354            updatePosition();
9355
9356            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9357                if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
9358                    TextViewPositionListener positionListener = mPositionListeners[i];
9359                    if (positionListener != null) {
9360                        positionListener.updatePosition(mPositionX, mPositionY,
9361                                mPositionHasChanged, mScrollHasChanged);
9362                    }
9363                }
9364            }
9365
9366            mScrollHasChanged = false;
9367            return true;
9368        }
9369
9370        private void updatePosition() {
9371            TextView.this.getLocationInWindow(mTempCoords);
9372
9373            mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
9374
9375            mPositionX = mTempCoords[0];
9376            mPositionY = mTempCoords[1];
9377        }
9378
9379        public void onScrollChanged() {
9380            mScrollHasChanged = true;
9381        }
9382    }
9383
9384    private boolean isPositionVisible(int positionX, int positionY) {
9385        synchronized (sTmpPosition) {
9386            final float[] position = sTmpPosition;
9387            position[0] = positionX;
9388            position[1] = positionY;
9389            View view = this;
9390
9391            while (view != null) {
9392                if (view != this) {
9393                    // Local scroll is already taken into account in positionX/Y
9394                    position[0] -= view.getScrollX();
9395                    position[1] -= view.getScrollY();
9396                }
9397
9398                if (position[0] < 0 || position[1] < 0 ||
9399                        position[0] > view.getWidth() || position[1] > view.getHeight()) {
9400                    return false;
9401                }
9402
9403                if (!view.getMatrix().isIdentity()) {
9404                    view.getMatrix().mapPoints(position);
9405                }
9406
9407                position[0] += view.getLeft();
9408                position[1] += view.getTop();
9409
9410                final ViewParent parent = view.getParent();
9411                if (parent instanceof View) {
9412                    view = (View) parent;
9413                } else {
9414                    // We've reached the ViewRoot, stop iterating
9415                    view = null;
9416                }
9417            }
9418        }
9419
9420        // We've been able to walk up the view hierarchy and the position was never clipped
9421        return true;
9422    }
9423
9424    private boolean isOffsetVisible(int offset) {
9425        final int line = mLayout.getLineForOffset(offset);
9426        final int lineBottom = mLayout.getLineBottom(line);
9427        final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
9428        return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
9429                lineBottom + viewportToContentVerticalOffset());
9430    }
9431
9432    @Override
9433    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9434        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9435        if (mPositionListener != null) {
9436            mPositionListener.onScrollChanged();
9437        }
9438    }
9439
9440    private abstract class PinnedPopupWindow implements TextViewPositionListener {
9441        protected PopupWindow mPopupWindow;
9442        protected ViewGroup mContentView;
9443        int mPositionX, mPositionY;
9444
9445        protected abstract void createPopupWindow();
9446        protected abstract void initContentView();
9447        protected abstract int getTextOffset();
9448        protected abstract int getVerticalLocalPosition(int line);
9449        protected abstract int clipVertically(int positionY);
9450
9451        public PinnedPopupWindow() {
9452            createPopupWindow();
9453
9454            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
9455            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
9456            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
9457
9458            initContentView();
9459
9460            LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
9461                    ViewGroup.LayoutParams.WRAP_CONTENT);
9462            mContentView.setLayoutParams(wrapContent);
9463
9464            mPopupWindow.setContentView(mContentView);
9465        }
9466
9467        public void show() {
9468            TextView.this.getPositionListener().addSubscriber(this, false /* offset is fixed */);
9469
9470            computeLocalPosition();
9471
9472            final PositionListener positionListener = TextView.this.getPositionListener();
9473            updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
9474        }
9475
9476        protected void measureContent() {
9477            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9478            mContentView.measure(
9479                    View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
9480                            View.MeasureSpec.AT_MOST),
9481                    View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
9482                            View.MeasureSpec.AT_MOST));
9483        }
9484
9485        /* The popup window will be horizontally centered on the getTextOffset() and vertically
9486         * positioned according to viewportToContentHorizontalOffset.
9487         *
9488         * This method assumes that mContentView has properly been measured from its content. */
9489        private void computeLocalPosition() {
9490            measureContent();
9491            final int width = mContentView.getMeasuredWidth();
9492            final int offset = getTextOffset();
9493            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
9494            mPositionX += viewportToContentHorizontalOffset();
9495
9496            final int line = mLayout.getLineForOffset(offset);
9497            mPositionY = getVerticalLocalPosition(line);
9498            mPositionY += viewportToContentVerticalOffset();
9499        }
9500
9501        private void updatePosition(int parentPositionX, int parentPositionY) {
9502            int positionX = parentPositionX + mPositionX;
9503            int positionY = parentPositionY + mPositionY;
9504
9505            positionY = clipVertically(positionY);
9506
9507            // Horizontal clipping
9508            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9509            final int width = mContentView.getMeasuredWidth();
9510            positionX = Math.min(displayMetrics.widthPixels - width, positionX);
9511            positionX = Math.max(0, positionX);
9512
9513            if (isShowing()) {
9514                mPopupWindow.update(positionX, positionY, -1, -1);
9515            } else {
9516                mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
9517                        positionX, positionY);
9518            }
9519        }
9520
9521        public void hide() {
9522            mPopupWindow.dismiss();
9523            TextView.this.getPositionListener().removeSubscriber(this);
9524        }
9525
9526        @Override
9527        public void updatePosition(int parentPositionX, int parentPositionY,
9528                boolean parentPositionChanged, boolean parentScrolled) {
9529            // Either parentPositionChanged or parentScrolled is true, check if still visible
9530            if (isShowing() && isOffsetVisible(getTextOffset())) {
9531                if (parentScrolled) computeLocalPosition();
9532                updatePosition(parentPositionX, parentPositionY);
9533            } else {
9534                hide();
9535            }
9536        }
9537
9538        public boolean isShowing() {
9539            return mPopupWindow.isShowing();
9540        }
9541    }
9542
9543    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
9544        private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
9545        private static final int ADD_TO_DICTIONARY = -1;
9546        private static final int DELETE_TEXT = -2;
9547        private SuggestionInfo[] mSuggestionInfos;
9548        private int mNumberOfSuggestions;
9549        private boolean mCursorWasVisibleBeforeSuggestions;
9550        private boolean mIsShowingUp = false;
9551        private SuggestionAdapter mSuggestionsAdapter;
9552        private final Comparator<SuggestionSpan> mSuggestionSpanComparator;
9553        private final HashMap<SuggestionSpan, Integer> mSpansLengths;
9554
9555        private class CustomPopupWindow extends PopupWindow {
9556            public CustomPopupWindow(Context context, int defStyle) {
9557                super(context, null, defStyle);
9558            }
9559
9560            @Override
9561            public void dismiss() {
9562                super.dismiss();
9563
9564                TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
9565
9566                // Safe cast since show() checks that mText is an Editable
9567                ((Spannable) mText).removeSpan(mSuggestionRangeSpan);
9568
9569                setCursorVisible(mCursorWasVisibleBeforeSuggestions);
9570                if (hasInsertionController()) {
9571                    getInsertionController().show();
9572                }
9573            }
9574        }
9575
9576        public SuggestionsPopupWindow() {
9577            mCursorWasVisibleBeforeSuggestions = mCursorVisible;
9578            mSuggestionSpanComparator = new SuggestionSpanComparator();
9579            mSpansLengths = new HashMap<SuggestionSpan, Integer>();
9580        }
9581
9582        @Override
9583        protected void createPopupWindow() {
9584            mPopupWindow = new CustomPopupWindow(TextView.this.mContext,
9585                com.android.internal.R.attr.textSuggestionsWindowStyle);
9586            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
9587            mPopupWindow.setFocusable(true);
9588            mPopupWindow.setClippingEnabled(false);
9589        }
9590
9591        @Override
9592        protected void initContentView() {
9593            ListView listView = new ListView(TextView.this.getContext());
9594            mSuggestionsAdapter = new SuggestionAdapter();
9595            listView.setAdapter(mSuggestionsAdapter);
9596            listView.setOnItemClickListener(this);
9597            mContentView = listView;
9598
9599            // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete
9600            mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2];
9601            for (int i = 0; i < mSuggestionInfos.length; i++) {
9602                mSuggestionInfos[i] = new SuggestionInfo();
9603            }
9604        }
9605
9606        public boolean isShowingUp() {
9607            return mIsShowingUp;
9608        }
9609
9610        public void onParentLostFocus() {
9611            mIsShowingUp = false;
9612        }
9613
9614        private class SuggestionInfo {
9615            int suggestionStart, suggestionEnd; // range of actual suggestion within text
9616            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
9617            int suggestionIndex; // the index of this suggestion inside suggestionSpan
9618            SpannableStringBuilder text = new SpannableStringBuilder();
9619            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
9620                    android.R.style.TextAppearance_SuggestionHighlight);
9621        }
9622
9623        private class SuggestionAdapter extends BaseAdapter {
9624            private LayoutInflater mInflater = (LayoutInflater) TextView.this.mContext.
9625                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9626
9627            @Override
9628            public int getCount() {
9629                return mNumberOfSuggestions;
9630            }
9631
9632            @Override
9633            public Object getItem(int position) {
9634                return mSuggestionInfos[position];
9635            }
9636
9637            @Override
9638            public long getItemId(int position) {
9639                return position;
9640            }
9641
9642            @Override
9643            public View getView(int position, View convertView, ViewGroup parent) {
9644                TextView textView = (TextView) convertView;
9645
9646                if (textView == null) {
9647                    textView = (TextView) mInflater.inflate(mTextEditSuggestionItemLayout, parent,
9648                            false);
9649                }
9650
9651                final SuggestionInfo suggestionInfo = mSuggestionInfos[position];
9652                textView.setText(suggestionInfo.text);
9653
9654                if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
9655                    textView.setCompoundDrawablesWithIntrinsicBounds(
9656                            com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0);
9657                } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
9658                    textView.setCompoundDrawablesWithIntrinsicBounds(
9659                            com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0);
9660                } else {
9661                    textView.setCompoundDrawables(null, null, null, null);
9662                }
9663
9664                return textView;
9665            }
9666        }
9667
9668        private class SuggestionSpanComparator implements Comparator<SuggestionSpan> {
9669            public int compare(SuggestionSpan span1, SuggestionSpan span2) {
9670                final int flag1 = span1.getFlags();
9671                final int flag2 = span2.getFlags();
9672                if (flag1 != flag2) {
9673                    // The order here should match what is used in updateDrawState
9674                    final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
9675                    final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
9676                    final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0;
9677                    final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0;
9678                    if (easy1 && !misspelled1) return -1;
9679                    if (easy2 && !misspelled2) return 1;
9680                    if (misspelled1) return -1;
9681                    if (misspelled2) return 1;
9682                }
9683
9684                return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue();
9685            }
9686        }
9687
9688        /**
9689         * Returns the suggestion spans that cover the current cursor position. The suggestion
9690         * spans are sorted according to the length of text that they are attached to.
9691         */
9692        private SuggestionSpan[] getSuggestionSpans() {
9693            int pos = TextView.this.getSelectionStart();
9694            Spannable spannable = (Spannable) TextView.this.mText;
9695            SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
9696
9697            mSpansLengths.clear();
9698            for (SuggestionSpan suggestionSpan : suggestionSpans) {
9699                int start = spannable.getSpanStart(suggestionSpan);
9700                int end = spannable.getSpanEnd(suggestionSpan);
9701                mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start));
9702            }
9703
9704            // The suggestions are sorted according to their types (easy correction first, then
9705            // misspelled) and to the length of the text that they cover (shorter first).
9706            Arrays.sort(suggestionSpans, mSuggestionSpanComparator);
9707            return suggestionSpans;
9708        }
9709
9710        @Override
9711        public void show() {
9712            if (!(mText instanceof Editable)) return;
9713
9714            if (updateSuggestions()) {
9715                mCursorWasVisibleBeforeSuggestions = mCursorVisible;
9716                setCursorVisible(false);
9717                mIsShowingUp = true;
9718                super.show();
9719            }
9720        }
9721
9722        @Override
9723        protected void measureContent() {
9724            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9725            final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
9726                    displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
9727            final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
9728                    displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
9729
9730            int width = 0;
9731            View view = null;
9732            for (int i = 0; i < mNumberOfSuggestions; i++) {
9733                view = mSuggestionsAdapter.getView(i, view, mContentView);
9734                view.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
9735                view.measure(horizontalMeasure, verticalMeasure);
9736                width = Math.max(width, view.getMeasuredWidth());
9737            }
9738
9739            // Enforce the width based on actual text widths
9740            mContentView.measure(
9741                    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
9742                    verticalMeasure);
9743
9744            Drawable popupBackground = mPopupWindow.getBackground();
9745            if (popupBackground != null) {
9746                if (mTempRect == null) mTempRect = new Rect();
9747                popupBackground.getPadding(mTempRect);
9748                width += mTempRect.left + mTempRect.right;
9749            }
9750            mPopupWindow.setWidth(width);
9751        }
9752
9753        @Override
9754        protected int getTextOffset() {
9755            return getSelectionStart();
9756        }
9757
9758        @Override
9759        protected int getVerticalLocalPosition(int line) {
9760            return mLayout.getLineBottom(line);
9761        }
9762
9763        @Override
9764        protected int clipVertically(int positionY) {
9765            final int height = mContentView.getMeasuredHeight();
9766            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9767            return Math.min(positionY, displayMetrics.heightPixels - height);
9768        }
9769
9770        @Override
9771        public void hide() {
9772            super.hide();
9773        }
9774
9775        private boolean updateSuggestions() {
9776            Spannable spannable = (Spannable) TextView.this.mText;
9777            SuggestionSpan[] suggestionSpans = getSuggestionSpans();
9778
9779            final int nbSpans = suggestionSpans.length;
9780            // Suggestions are shown after a delay: the underlying spans may have been removed
9781            if (nbSpans == 0) return false;
9782
9783            mNumberOfSuggestions = 0;
9784            int spanUnionStart = mText.length();
9785            int spanUnionEnd = 0;
9786
9787            SuggestionSpan misspelledSpan = null;
9788            int underlineColor = 0;
9789
9790            for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
9791                SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
9792                final int spanStart = spannable.getSpanStart(suggestionSpan);
9793                final int spanEnd = spannable.getSpanEnd(suggestionSpan);
9794                spanUnionStart = Math.min(spanStart, spanUnionStart);
9795                spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
9796
9797                if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
9798                    misspelledSpan = suggestionSpan;
9799                }
9800
9801                // The first span dictates the background color of the highlighted text
9802                if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor();
9803
9804                String[] suggestions = suggestionSpan.getSuggestions();
9805                int nbSuggestions = suggestions.length;
9806                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
9807                    String suggestion = suggestions[suggestionIndex];
9808
9809                    boolean suggestionIsDuplicate = false;
9810                    for (int i = 0; i < mNumberOfSuggestions; i++) {
9811                        if (mSuggestionInfos[i].text.toString().equals(suggestion)) {
9812                            SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan;
9813                            final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan);
9814                            final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan);
9815                            if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
9816                                suggestionIsDuplicate = true;
9817                                break;
9818                            }
9819                        }
9820                    }
9821
9822                    if (!suggestionIsDuplicate) {
9823                        SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
9824                        suggestionInfo.suggestionSpan = suggestionSpan;
9825                        suggestionInfo.suggestionIndex = suggestionIndex;
9826                        suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion);
9827
9828                        mNumberOfSuggestions++;
9829
9830                        if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
9831                            // Also end outer for loop
9832                            spanIndex = nbSpans;
9833                            break;
9834                        }
9835                    }
9836                }
9837            }
9838
9839            for (int i = 0; i < mNumberOfSuggestions; i++) {
9840                highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
9841            }
9842
9843            // Add "Add to dictionary" item if there is a span with the misspelled flag
9844            if (misspelledSpan != null) {
9845                final int misspelledStart = spannable.getSpanStart(misspelledSpan);
9846                final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
9847                if (misspelledStart >= 0 && misspelledEnd > misspelledStart) {
9848                    SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
9849                    suggestionInfo.suggestionSpan = misspelledSpan;
9850                    suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY;
9851                    suggestionInfo.text.replace(0, suggestionInfo.text.length(),
9852                            getContext().getString(com.android.internal.R.string.addToDictionary));
9853                    suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
9854                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9855
9856                    mNumberOfSuggestions++;
9857                }
9858            }
9859
9860            // Delete item
9861            SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
9862            suggestionInfo.suggestionSpan = null;
9863            suggestionInfo.suggestionIndex = DELETE_TEXT;
9864            suggestionInfo.text.replace(0, suggestionInfo.text.length(),
9865                    getContext().getString(com.android.internal.R.string.deleteText));
9866            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
9867                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9868            mNumberOfSuggestions++;
9869
9870            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
9871            if (underlineColor == 0) {
9872                // Fallback on the default highlight color when the first span does not provide one
9873                mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
9874            } else {
9875                final float BACKGROUND_TRANSPARENCY = 0.4f;
9876                final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
9877                mSuggestionRangeSpan.setBackgroundColor(
9878                        (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
9879            }
9880            spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
9881                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9882
9883            mSuggestionsAdapter.notifyDataSetChanged();
9884            return true;
9885        }
9886
9887        private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
9888                int unionEnd) {
9889            final Spannable text = (Spannable) mText;
9890            final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan);
9891            final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan);
9892
9893            // Adjust the start/end of the suggestion span
9894            suggestionInfo.suggestionStart = spanStart - unionStart;
9895            suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart
9896                    + suggestionInfo.text.length();
9897
9898            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0,
9899                    suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9900
9901            // Add the text before and after the span.
9902            final String textAsString = text.toString();
9903            suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart));
9904            suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd));
9905        }
9906
9907        @Override
9908        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
9909            Editable editable = (Editable) mText;
9910            SuggestionInfo suggestionInfo = mSuggestionInfos[position];
9911
9912            if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
9913                final int spanUnionStart = editable.getSpanStart(mSuggestionRangeSpan);
9914                int spanUnionEnd = editable.getSpanEnd(mSuggestionRangeSpan);
9915                if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) {
9916                    // Do not leave two adjacent spaces after deletion, or one at beginning of text
9917                    if (spanUnionEnd < editable.length() &&
9918                            Character.isSpaceChar(editable.charAt(spanUnionEnd)) &&
9919                            (spanUnionStart == 0 ||
9920                            Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) {
9921                        spanUnionEnd = spanUnionEnd + 1;
9922                    }
9923                    deleteText_internal(spanUnionStart, spanUnionEnd);
9924                }
9925                hide();
9926                return;
9927            }
9928
9929            final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan);
9930            final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
9931            if (spanStart < 0 || spanEnd <= spanStart) {
9932                // Span has been removed
9933                hide();
9934                return;
9935            }
9936            final String originalText = mText.toString().substring(spanStart, spanEnd);
9937
9938            if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
9939                Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
9940                intent.putExtra("word", originalText);
9941                intent.putExtra("locale", getTextServicesLocale().toString());
9942                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
9943                getContext().startActivity(intent);
9944                // There is no way to know if the word was indeed added. Re-check.
9945                // TODO The ExtractEditText should remove the span in the original text instead
9946                editable.removeSpan(suggestionInfo.suggestionSpan);
9947                updateSpellCheckSpans(spanStart, spanEnd, false);
9948            } else {
9949                // SuggestionSpans are removed by replace: save them before
9950                SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
9951                        SuggestionSpan.class);
9952                final int length = suggestionSpans.length;
9953                int[] suggestionSpansStarts = new int[length];
9954                int[] suggestionSpansEnds = new int[length];
9955                int[] suggestionSpansFlags = new int[length];
9956                for (int i = 0; i < length; i++) {
9957                    final SuggestionSpan suggestionSpan = suggestionSpans[i];
9958                    suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
9959                    suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
9960                    suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
9961
9962                    // Remove potential misspelled flags
9963                    int suggestionSpanFlags = suggestionSpan.getFlags();
9964                    if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
9965                        suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
9966                        suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
9967                        suggestionSpan.setFlags(suggestionSpanFlags);
9968                    }
9969                }
9970
9971                final int suggestionStart = suggestionInfo.suggestionStart;
9972                final int suggestionEnd = suggestionInfo.suggestionEnd;
9973                final String suggestion = suggestionInfo.text.subSequence(
9974                        suggestionStart, suggestionEnd).toString();
9975                replaceText_internal(spanStart, spanEnd, suggestion);
9976
9977                // Notify source IME of the suggestion pick. Do this before swaping texts.
9978                if (!TextUtils.isEmpty(
9979                        suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
9980                    InputMethodManager imm = InputMethodManager.peekInstance();
9981                    if (imm != null) {
9982                        imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
9983                                suggestionInfo.suggestionIndex);
9984                    }
9985                }
9986
9987                // Swap text content between actual text and Suggestion span
9988                String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
9989                suggestions[suggestionInfo.suggestionIndex] = originalText;
9990
9991                // Restore previous SuggestionSpans
9992                final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
9993                for (int i = 0; i < length; i++) {
9994                    // Only spans that include the modified region make sense after replacement
9995                    // Spans partially included in the replaced region are removed, there is no
9996                    // way to assign them a valid range after replacement
9997                    if (suggestionSpansStarts[i] <= spanStart &&
9998                            suggestionSpansEnds[i] >= spanEnd) {
9999                        setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i],
10000                                suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]);
10001                    }
10002                }
10003
10004                // Move cursor at the end of the replaced word
10005                final int newCursorPosition = spanEnd + lengthDifference;
10006                setCursorPosition_internal(newCursorPosition, newCursorPosition);
10007            }
10008
10009            hide();
10010        }
10011    }
10012
10013    /**
10014     * Removes the suggestion spans.
10015     */
10016    CharSequence removeSuggestionSpans(CharSequence text) {
10017       if (text instanceof Spanned) {
10018           Spannable spannable;
10019           if (text instanceof Spannable) {
10020               spannable = (Spannable) text;
10021           } else {
10022               spannable = new SpannableString(text);
10023               text = spannable;
10024           }
10025
10026           SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
10027           for (int i = 0; i < spans.length; i++) {
10028               spannable.removeSpan(spans[i]);
10029           }
10030       }
10031       return text;
10032    }
10033
10034    void showSuggestions() {
10035        if (mSuggestionsPopupWindow == null) {
10036            mSuggestionsPopupWindow = new SuggestionsPopupWindow();
10037        }
10038        hideControllers();
10039        mSuggestionsPopupWindow.show();
10040    }
10041
10042    boolean areSuggestionsShown() {
10043        return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
10044    }
10045
10046    /**
10047     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
10048     * by the IME or by the spell checker as the user types. This is done by adding
10049     * {@link SuggestionSpan}s to the text.
10050     *
10051     * When suggestions are enabled (default), this list of suggestions will be displayed when the
10052     * user asks for them on these parts of the text. This value depends on the inputType of this
10053     * TextView.
10054     *
10055     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
10056     *
10057     * In addition, the type variation must be one of
10058     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
10059     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
10060     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
10061     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
10062     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
10063     *
10064     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
10065     *
10066     * @return true if the suggestions popup window is enabled, based on the inputType.
10067     */
10068    public boolean isSuggestionsEnabled() {
10069        if ((mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
10070        if ((mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
10071
10072        final int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
10073        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
10074                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
10075                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
10076                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
10077                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
10078    }
10079
10080    /**
10081     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10082     * selection is initiated in this View.
10083     *
10084     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
10085     * Paste actions, depending on what this View supports.
10086     *
10087     * A custom implementation can add new entries in the default menu in its
10088     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
10089     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
10090     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
10091     * or {@link android.R.id#paste} ids as parameters.
10092     *
10093     * Returning false from
10094     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
10095     * the action mode from being started.
10096     *
10097     * Action click events should be handled by the custom implementation of
10098     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
10099     *
10100     * Note that text selection mode is not started when a TextView receives focus and the
10101     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
10102     * that case, to allow for quick replacement.
10103     */
10104    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
10105        mCustomSelectionActionModeCallback = actionModeCallback;
10106    }
10107
10108    /**
10109     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
10110     *
10111     * @return The current custom selection callback.
10112     */
10113    public ActionMode.Callback getCustomSelectionActionModeCallback() {
10114        return mCustomSelectionActionModeCallback;
10115    }
10116
10117    /**
10118     *
10119     * @return true if the selection mode was actually started.
10120     */
10121    private boolean startSelectionActionMode() {
10122        if (mSelectionActionMode != null) {
10123            // Selection action mode is already started
10124            return false;
10125        }
10126
10127        if (!canSelectText() || !requestFocus()) {
10128            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
10129            return false;
10130        }
10131
10132        if (!hasSelection()) {
10133            // There may already be a selection on device rotation
10134            if (!selectCurrentWord()) {
10135                // No word found under cursor or text selection not permitted.
10136                return false;
10137            }
10138        }
10139
10140        boolean willExtract = extractedTextModeWillBeStarted();
10141
10142        // Do not start the action mode when extracted text will show up full screen, which would
10143        // immediately hide the newly created action bar and would be visually distracting.
10144        if (!willExtract) {
10145            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
10146            mSelectionActionMode = startActionMode(actionModeCallback);
10147        }
10148
10149        final boolean selectionStarted = mSelectionActionMode != null || willExtract;
10150        if (selectionStarted && !mTextIsSelectable) {
10151            // Show the IME to be able to replace text, except when selecting non editable text.
10152            final InputMethodManager imm = InputMethodManager.peekInstance();
10153            if (imm != null) {
10154                imm.showSoftInput(this, 0, null);
10155            }
10156        }
10157
10158        return selectionStarted;
10159    }
10160
10161    private boolean extractedTextModeWillBeStarted() {
10162        if (!(this instanceof ExtractEditText)) {
10163            final InputMethodManager imm = InputMethodManager.peekInstance();
10164            return  imm != null && imm.isFullscreenMode();
10165        }
10166        return false;
10167    }
10168
10169    /**
10170     * @hide
10171     */
10172    protected void stopSelectionActionMode() {
10173        if (mSelectionActionMode != null) {
10174            // This will hide the mSelectionModifierCursorController
10175            mSelectionActionMode.finish();
10176        }
10177    }
10178
10179    /**
10180     * Paste clipboard content between min and max positions.
10181     */
10182    private void paste(int min, int max) {
10183        ClipboardManager clipboard =
10184            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
10185        ClipData clip = clipboard.getPrimaryClip();
10186        if (clip != null) {
10187            boolean didFirst = false;
10188            for (int i=0; i<clip.getItemCount(); i++) {
10189                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
10190                if (paste != null) {
10191                    if (!didFirst) {
10192                        long minMax = prepareSpacesAroundPaste(min, max, paste);
10193                        min = extractRangeStartFromLong(minMax);
10194                        max = extractRangeEndFromLong(minMax);
10195                        Selection.setSelection((Spannable) mText, max);
10196                        ((Editable) mText).replace(min, max, paste);
10197                        didFirst = true;
10198                    } else {
10199                        ((Editable) mText).insert(getSelectionEnd(), "\n");
10200                        ((Editable) mText).insert(getSelectionEnd(), paste);
10201                    }
10202                }
10203            }
10204            stopSelectionActionMode();
10205            sLastCutOrCopyTime = 0;
10206        }
10207    }
10208
10209    private void setPrimaryClip(ClipData clip) {
10210        ClipboardManager clipboard = (ClipboardManager) getContext().
10211                getSystemService(Context.CLIPBOARD_SERVICE);
10212        clipboard.setPrimaryClip(clip);
10213        sLastCutOrCopyTime = SystemClock.uptimeMillis();
10214    }
10215
10216    /**
10217     * An ActionMode Callback class that is used to provide actions while in text selection mode.
10218     *
10219     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
10220     * on which of these this TextView supports.
10221     */
10222    private class SelectionActionModeCallback implements ActionMode.Callback {
10223
10224        @Override
10225        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
10226            TypedArray styledAttributes = mContext.obtainStyledAttributes(
10227                    com.android.internal.R.styleable.SelectionModeDrawables);
10228
10229            boolean allowText = getContext().getResources().getBoolean(
10230                    com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
10231
10232            mode.setTitle(allowText ?
10233                    mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
10234            mode.setSubtitle(null);
10235
10236            int selectAllIconId = 0; // No icon by default
10237            if (!allowText) {
10238                // Provide an icon, text will not be displayed on smaller screens.
10239                selectAllIconId = styledAttributes.getResourceId(
10240                        R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0);
10241            }
10242
10243            menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
10244                    setIcon(selectAllIconId).
10245                    setAlphabeticShortcut('a').
10246                    setShowAsAction(
10247                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10248
10249            if (canCut()) {
10250                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
10251                    setIcon(styledAttributes.getResourceId(
10252                            R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)).
10253                    setAlphabeticShortcut('x').
10254                    setShowAsAction(
10255                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10256            }
10257
10258            if (canCopy()) {
10259                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
10260                    setIcon(styledAttributes.getResourceId(
10261                            R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)).
10262                    setAlphabeticShortcut('c').
10263                    setShowAsAction(
10264                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10265            }
10266
10267            if (canPaste()) {
10268                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
10269                        setIcon(styledAttributes.getResourceId(
10270                                R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)).
10271                        setAlphabeticShortcut('v').
10272                        setShowAsAction(
10273                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10274            }
10275
10276            styledAttributes.recycle();
10277
10278            if (mCustomSelectionActionModeCallback != null) {
10279                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
10280                    // The custom mode can choose to cancel the action mode
10281                    return false;
10282                }
10283            }
10284
10285            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
10286                getSelectionController().show();
10287                return true;
10288            } else {
10289                return false;
10290            }
10291        }
10292
10293        @Override
10294        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
10295            if (mCustomSelectionActionModeCallback != null) {
10296                return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
10297            }
10298            return true;
10299        }
10300
10301        @Override
10302        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
10303            if (mCustomSelectionActionModeCallback != null &&
10304                 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
10305                return true;
10306            }
10307            return onTextContextMenuItem(item.getItemId());
10308        }
10309
10310        @Override
10311        public void onDestroyActionMode(ActionMode mode) {
10312            if (mCustomSelectionActionModeCallback != null) {
10313                mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
10314            }
10315            Selection.setSelection((Spannable) mText, getSelectionEnd());
10316
10317            if (mSelectionModifierCursorController != null) {
10318                mSelectionModifierCursorController.hide();
10319            }
10320
10321            mSelectionActionMode = null;
10322        }
10323    }
10324
10325    private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
10326        private static final int POPUP_TEXT_LAYOUT =
10327                com.android.internal.R.layout.text_edit_action_popup_text;
10328        private TextView mPasteTextView;
10329        private TextView mReplaceTextView;
10330
10331        @Override
10332        protected void createPopupWindow() {
10333            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
10334                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10335            mPopupWindow.setClippingEnabled(true);
10336        }
10337
10338        @Override
10339        protected void initContentView() {
10340            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
10341            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
10342            mContentView = linearLayout;
10343            mContentView.setBackgroundResource(
10344                    com.android.internal.R.drawable.text_edit_paste_window);
10345
10346            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
10347                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
10348
10349            LayoutParams wrapContent = new LayoutParams(
10350                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
10351
10352            mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
10353            mPasteTextView.setLayoutParams(wrapContent);
10354            mContentView.addView(mPasteTextView);
10355            mPasteTextView.setText(com.android.internal.R.string.paste);
10356            mPasteTextView.setOnClickListener(this);
10357
10358            mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
10359            mReplaceTextView.setLayoutParams(wrapContent);
10360            mContentView.addView(mReplaceTextView);
10361            mReplaceTextView.setText(com.android.internal.R.string.replace);
10362            mReplaceTextView.setOnClickListener(this);
10363        }
10364
10365        @Override
10366        public void show() {
10367            boolean canPaste = canPaste();
10368            boolean canSuggest = isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
10369            mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE);
10370            mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE);
10371
10372            if (!canPaste && !canSuggest) return;
10373
10374            super.show();
10375        }
10376
10377        @Override
10378        public void onClick(View view) {
10379            if (view == mPasteTextView && canPaste()) {
10380                onTextContextMenuItem(ID_PASTE);
10381                hide();
10382            } else if (view == mReplaceTextView) {
10383                final int middle = (getSelectionStart() + getSelectionEnd()) / 2;
10384                stopSelectionActionMode();
10385                Selection.setSelection((Spannable) mText, middle);
10386                showSuggestions();
10387            }
10388        }
10389
10390        @Override
10391        protected int getTextOffset() {
10392            return (getSelectionStart() + getSelectionEnd()) / 2;
10393        }
10394
10395        @Override
10396        protected int getVerticalLocalPosition(int line) {
10397            return mLayout.getLineTop(line) - mContentView.getMeasuredHeight();
10398        }
10399
10400        @Override
10401        protected int clipVertically(int positionY) {
10402            if (positionY < 0) {
10403                final int offset = getTextOffset();
10404                final int line = mLayout.getLineForOffset(offset);
10405                positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line);
10406                positionY += mContentView.getMeasuredHeight();
10407
10408                // Assumes insertion and selection handles share the same height
10409                final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
10410                positionY += handle.getIntrinsicHeight();
10411            }
10412
10413            return positionY;
10414        }
10415    }
10416
10417    private abstract class HandleView extends View implements TextViewPositionListener {
10418        protected Drawable mDrawable;
10419        protected Drawable mDrawableLtr;
10420        protected Drawable mDrawableRtl;
10421        private final PopupWindow mContainer;
10422        // Position with respect to the parent TextView
10423        private int mPositionX, mPositionY;
10424        private boolean mIsDragging;
10425        // Offset from touch position to mPosition
10426        private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
10427        protected int mHotspotX;
10428        // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
10429        private float mTouchOffsetY;
10430        // Where the touch position should be on the handle to ensure a maximum cursor visibility
10431        private float mIdealVerticalOffset;
10432        // Parent's (TextView) previous position in window
10433        private int mLastParentX, mLastParentY;
10434        // Transient action popup window for Paste and Replace actions
10435        protected ActionPopupWindow mActionPopupWindow;
10436        // Previous text character offset
10437        private int mPreviousOffset = -1;
10438        // Previous text character offset
10439        private boolean mPositionHasChanged = true;
10440        // Used to delay the appearance of the action popup window
10441        private Runnable mActionPopupShower;
10442
10443        public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
10444            super(TextView.this.mContext);
10445            mContainer = new PopupWindow(TextView.this.mContext, null,
10446                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10447            mContainer.setSplitTouchEnabled(true);
10448            mContainer.setClippingEnabled(false);
10449            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
10450            mContainer.setContentView(this);
10451
10452            mDrawableLtr = drawableLtr;
10453            mDrawableRtl = drawableRtl;
10454
10455            updateDrawable();
10456
10457            final int handleHeight = mDrawable.getIntrinsicHeight();
10458            mTouchOffsetY = -0.3f * handleHeight;
10459            mIdealVerticalOffset = 0.7f * handleHeight;
10460        }
10461
10462        protected void updateDrawable() {
10463            final int offset = getCurrentCursorOffset();
10464            final boolean isRtlCharAtOffset = mLayout.isRtlCharAt(offset);
10465            mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
10466            mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
10467        }
10468
10469        protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
10470
10471        // Touch-up filter: number of previous positions remembered
10472        private static final int HISTORY_SIZE = 5;
10473        private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
10474        private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
10475        private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
10476        private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
10477        private int mPreviousOffsetIndex = 0;
10478        private int mNumberPreviousOffsets = 0;
10479
10480        private void startTouchUpFilter(int offset) {
10481            mNumberPreviousOffsets = 0;
10482            addPositionToTouchUpFilter(offset);
10483        }
10484
10485        private void addPositionToTouchUpFilter(int offset) {
10486            mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
10487            mPreviousOffsets[mPreviousOffsetIndex] = offset;
10488            mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
10489            mNumberPreviousOffsets++;
10490        }
10491
10492        private void filterOnTouchUp() {
10493            final long now = SystemClock.uptimeMillis();
10494            int i = 0;
10495            int index = mPreviousOffsetIndex;
10496            final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
10497            while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
10498                i++;
10499                index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
10500            }
10501
10502            if (i > 0 && i < iMax &&
10503                    (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
10504                positionAtCursorOffset(mPreviousOffsets[index], false);
10505            }
10506        }
10507
10508        public boolean offsetHasBeenChanged() {
10509            return mNumberPreviousOffsets > 1;
10510        }
10511
10512        @Override
10513        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
10514            setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
10515        }
10516
10517        public void show() {
10518            if (isShowing()) return;
10519
10520            getPositionListener().addSubscriber(this, true /* local position may change */);
10521
10522            // Make sure the offset is always considered new, even when focusing at same position
10523            mPreviousOffset = -1;
10524            positionAtCursorOffset(getCurrentCursorOffset(), false);
10525
10526            hideActionPopupWindow();
10527        }
10528
10529        protected void dismiss() {
10530            mIsDragging = false;
10531            mContainer.dismiss();
10532            onDetached();
10533        }
10534
10535        public void hide() {
10536            dismiss();
10537
10538            TextView.this.getPositionListener().removeSubscriber(this);
10539        }
10540
10541        void showActionPopupWindow(int delay) {
10542            if (mActionPopupWindow == null) {
10543                mActionPopupWindow = new ActionPopupWindow();
10544            }
10545            if (mActionPopupShower == null) {
10546                mActionPopupShower = new Runnable() {
10547                    public void run() {
10548                        mActionPopupWindow.show();
10549                    }
10550                };
10551            } else {
10552                TextView.this.removeCallbacks(mActionPopupShower);
10553            }
10554            TextView.this.postDelayed(mActionPopupShower, delay);
10555        }
10556
10557        protected void hideActionPopupWindow() {
10558            if (mActionPopupShower != null) {
10559                TextView.this.removeCallbacks(mActionPopupShower);
10560            }
10561            if (mActionPopupWindow != null) {
10562                mActionPopupWindow.hide();
10563            }
10564        }
10565
10566        public boolean isShowing() {
10567            return mContainer.isShowing();
10568        }
10569
10570        private boolean isVisible() {
10571            // Always show a dragging handle.
10572            if (mIsDragging) {
10573                return true;
10574            }
10575
10576            if (isInBatchEditMode()) {
10577                return false;
10578            }
10579
10580            return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY);
10581        }
10582
10583        public abstract int getCurrentCursorOffset();
10584
10585        protected abstract void updateSelection(int offset);
10586
10587        public abstract void updatePosition(float x, float y);
10588
10589        protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
10590            // A HandleView relies on the layout, which may be nulled by external methods
10591            if (mLayout == null) {
10592                // Will update controllers' state, hiding them and stopping selection mode if needed
10593                prepareCursorControllers();
10594                return;
10595            }
10596
10597            if (offset != mPreviousOffset || parentScrolled) {
10598                updateSelection(offset);
10599                addPositionToTouchUpFilter(offset);
10600                final int line = mLayout.getLineForOffset(offset);
10601
10602                mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
10603                mPositionY = mLayout.getLineBottom(line);
10604
10605                // Take TextView's padding and scroll into account.
10606                mPositionX += viewportToContentHorizontalOffset();
10607                mPositionY += viewportToContentVerticalOffset();
10608
10609                mPreviousOffset = offset;
10610                mPositionHasChanged = true;
10611            }
10612        }
10613
10614        public void updatePosition(int parentPositionX, int parentPositionY,
10615                boolean parentPositionChanged, boolean parentScrolled) {
10616            positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
10617            if (parentPositionChanged || mPositionHasChanged) {
10618                if (mIsDragging) {
10619                    // Update touchToWindow offset in case of parent scrolling while dragging
10620                    if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
10621                        mTouchToWindowOffsetX += parentPositionX - mLastParentX;
10622                        mTouchToWindowOffsetY += parentPositionY - mLastParentY;
10623                        mLastParentX = parentPositionX;
10624                        mLastParentY = parentPositionY;
10625                    }
10626
10627                    onHandleMoved();
10628                }
10629
10630                if (isVisible()) {
10631                    final int positionX = parentPositionX + mPositionX;
10632                    final int positionY = parentPositionY + mPositionY;
10633                    if (isShowing()) {
10634                        mContainer.update(positionX, positionY, -1, -1);
10635                    } else {
10636                        mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
10637                                positionX, positionY);
10638                    }
10639                } else {
10640                    if (isShowing()) {
10641                        dismiss();
10642                    }
10643                }
10644
10645                mPositionHasChanged = false;
10646            }
10647        }
10648
10649        @Override
10650        protected void onDraw(Canvas c) {
10651            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
10652            mDrawable.draw(c);
10653        }
10654
10655        @Override
10656        public boolean onTouchEvent(MotionEvent ev) {
10657            switch (ev.getActionMasked()) {
10658                case MotionEvent.ACTION_DOWN: {
10659                    startTouchUpFilter(getCurrentCursorOffset());
10660                    mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
10661                    mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
10662
10663                    final PositionListener positionListener = getPositionListener();
10664                    mLastParentX = positionListener.getPositionX();
10665                    mLastParentY = positionListener.getPositionY();
10666                    mIsDragging = true;
10667                    break;
10668                }
10669
10670                case MotionEvent.ACTION_MOVE: {
10671                    final float rawX = ev.getRawX();
10672                    final float rawY = ev.getRawY();
10673
10674                    // Vertical hysteresis: vertical down movement tends to snap to ideal offset
10675                    final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
10676                    final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
10677                    float newVerticalOffset;
10678                    if (previousVerticalOffset < mIdealVerticalOffset) {
10679                        newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
10680                        newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
10681                    } else {
10682                        newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
10683                        newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
10684                    }
10685                    mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
10686
10687                    final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
10688                    final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
10689
10690                    updatePosition(newPosX, newPosY);
10691                    break;
10692                }
10693
10694                case MotionEvent.ACTION_UP:
10695                    filterOnTouchUp();
10696                    mIsDragging = false;
10697                    break;
10698
10699                case MotionEvent.ACTION_CANCEL:
10700                    mIsDragging = false;
10701                    break;
10702            }
10703            return true;
10704        }
10705
10706        public boolean isDragging() {
10707            return mIsDragging;
10708        }
10709
10710        void onHandleMoved() {
10711            hideActionPopupWindow();
10712        }
10713
10714        public void onDetached() {
10715            hideActionPopupWindow();
10716        }
10717    }
10718
10719    private class InsertionHandleView extends HandleView {
10720        private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
10721        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
10722
10723        // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow
10724        private float mDownPositionX, mDownPositionY;
10725        private Runnable mHider;
10726
10727        public InsertionHandleView(Drawable drawable) {
10728            super(drawable, drawable);
10729        }
10730
10731        @Override
10732        public void show() {
10733            super.show();
10734
10735            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
10736            if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
10737                showActionPopupWindow(0);
10738            }
10739
10740            hideAfterDelay();
10741        }
10742
10743        public void showWithActionPopup() {
10744            show();
10745            showActionPopupWindow(0);
10746        }
10747
10748        private void hideAfterDelay() {
10749            removeHiderCallback();
10750            if (mHider == null) {
10751                mHider = new Runnable() {
10752                    public void run() {
10753                        hide();
10754                    }
10755                };
10756            }
10757            TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
10758        }
10759
10760        private void removeHiderCallback() {
10761            if (mHider != null) {
10762                TextView.this.removeCallbacks(mHider);
10763            }
10764        }
10765
10766        @Override
10767        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10768            return drawable.getIntrinsicWidth() / 2;
10769        }
10770
10771        @Override
10772        public boolean onTouchEvent(MotionEvent ev) {
10773            final boolean result = super.onTouchEvent(ev);
10774
10775            switch (ev.getActionMasked()) {
10776                case MotionEvent.ACTION_DOWN:
10777                    mDownPositionX = ev.getRawX();
10778                    mDownPositionY = ev.getRawY();
10779                    break;
10780
10781                case MotionEvent.ACTION_UP:
10782                    if (!offsetHasBeenChanged()) {
10783                        final float deltaX = mDownPositionX - ev.getRawX();
10784                        final float deltaY = mDownPositionY - ev.getRawY();
10785                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
10786
10787                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
10788                                TextView.this.getContext());
10789                        final int touchSlop = viewConfiguration.getScaledTouchSlop();
10790
10791                        if (distanceSquared < touchSlop * touchSlop) {
10792                            if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
10793                                // Tapping on the handle dismisses the displayed action popup
10794                                mActionPopupWindow.hide();
10795                            } else {
10796                                showWithActionPopup();
10797                            }
10798                        }
10799                    }
10800                    hideAfterDelay();
10801                    break;
10802
10803                case MotionEvent.ACTION_CANCEL:
10804                    hideAfterDelay();
10805                    break;
10806
10807                default:
10808                    break;
10809            }
10810
10811            return result;
10812        }
10813
10814        @Override
10815        public int getCurrentCursorOffset() {
10816            return TextView.this.getSelectionStart();
10817        }
10818
10819        @Override
10820        public void updateSelection(int offset) {
10821            Selection.setSelection((Spannable) mText, offset);
10822        }
10823
10824        @Override
10825        public void updatePosition(float x, float y) {
10826            positionAtCursorOffset(getOffsetForPosition(x, y), false);
10827        }
10828
10829        @Override
10830        void onHandleMoved() {
10831            super.onHandleMoved();
10832            removeHiderCallback();
10833        }
10834
10835        @Override
10836        public void onDetached() {
10837            super.onDetached();
10838            removeHiderCallback();
10839        }
10840    }
10841
10842    private class SelectionStartHandleView extends HandleView {
10843
10844        public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
10845            super(drawableLtr, drawableRtl);
10846        }
10847
10848        @Override
10849        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10850            if (isRtlRun) {
10851                return drawable.getIntrinsicWidth() / 4;
10852            } else {
10853                return (drawable.getIntrinsicWidth() * 3) / 4;
10854            }
10855        }
10856
10857        @Override
10858        public int getCurrentCursorOffset() {
10859            return TextView.this.getSelectionStart();
10860        }
10861
10862        @Override
10863        public void updateSelection(int offset) {
10864            Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
10865            updateDrawable();
10866        }
10867
10868        @Override
10869        public void updatePosition(float x, float y) {
10870            int offset = getOffsetForPosition(x, y);
10871
10872            // Handles can not cross and selection is at least one character
10873            final int selectionEnd = getSelectionEnd();
10874            if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
10875
10876            positionAtCursorOffset(offset, false);
10877        }
10878
10879        public ActionPopupWindow getActionPopupWindow() {
10880            return mActionPopupWindow;
10881        }
10882    }
10883
10884    private class SelectionEndHandleView extends HandleView {
10885
10886        public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
10887            super(drawableLtr, drawableRtl);
10888        }
10889
10890        @Override
10891        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10892            if (isRtlRun) {
10893                return (drawable.getIntrinsicWidth() * 3) / 4;
10894            } else {
10895                return drawable.getIntrinsicWidth() / 4;
10896            }
10897        }
10898
10899        @Override
10900        public int getCurrentCursorOffset() {
10901            return TextView.this.getSelectionEnd();
10902        }
10903
10904        @Override
10905        public void updateSelection(int offset) {
10906            Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
10907            updateDrawable();
10908        }
10909
10910        @Override
10911        public void updatePosition(float x, float y) {
10912            int offset = getOffsetForPosition(x, y);
10913
10914            // Handles can not cross and selection is at least one character
10915            final int selectionStart = getSelectionStart();
10916            if (offset <= selectionStart) offset = Math.min(selectionStart + 1, mText.length());
10917
10918            positionAtCursorOffset(offset, false);
10919        }
10920
10921        public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
10922            mActionPopupWindow = actionPopupWindow;
10923        }
10924    }
10925
10926    /**
10927     * A CursorController instance can be used to control a cursor in the text.
10928     * It is not used outside of {@link TextView}.
10929     * @hide
10930     */
10931    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
10932        /**
10933         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
10934         * See also {@link #hide()}.
10935         */
10936        public void show();
10937
10938        /**
10939         * Hide the cursor controller from screen.
10940         * See also {@link #show()}.
10941         */
10942        public void hide();
10943
10944        /**
10945         * Called when the view is detached from window. Perform house keeping task, such as
10946         * stopping Runnable thread that would otherwise keep a reference on the context, thus
10947         * preventing the activity from being recycled.
10948         */
10949        public void onDetached();
10950    }
10951
10952    private class InsertionPointCursorController implements CursorController {
10953        private InsertionHandleView mHandle;
10954
10955        public void show() {
10956            getHandle().show();
10957        }
10958
10959        public void showWithActionPopup() {
10960            getHandle().showWithActionPopup();
10961        }
10962
10963        public void hide() {
10964            if (mHandle != null) {
10965                mHandle.hide();
10966            }
10967        }
10968
10969        public void onTouchModeChanged(boolean isInTouchMode) {
10970            if (!isInTouchMode) {
10971                hide();
10972            }
10973        }
10974
10975        private InsertionHandleView getHandle() {
10976            if (mSelectHandleCenter == null) {
10977                mSelectHandleCenter = mContext.getResources().getDrawable(
10978                        mTextSelectHandleRes);
10979            }
10980            if (mHandle == null) {
10981                mHandle = new InsertionHandleView(mSelectHandleCenter);
10982            }
10983            return mHandle;
10984        }
10985
10986        @Override
10987        public void onDetached() {
10988            final ViewTreeObserver observer = getViewTreeObserver();
10989            observer.removeOnTouchModeChangeListener(this);
10990
10991            if (mHandle != null) mHandle.onDetached();
10992        }
10993    }
10994
10995    private class SelectionModifierCursorController implements CursorController {
10996        private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds
10997        // The cursor controller handles, lazily created when shown.
10998        private SelectionStartHandleView mStartHandle;
10999        private SelectionEndHandleView mEndHandle;
11000        // The offsets of that last touch down event. Remembered to start selection there.
11001        private int mMinTouchOffset, mMaxTouchOffset;
11002
11003        // Double tap detection
11004        private long mPreviousTapUpTime = 0;
11005        private float mDownPositionX, mDownPositionY;
11006        private boolean mGestureStayedInTapRegion;
11007
11008        SelectionModifierCursorController() {
11009            resetTouchOffsets();
11010        }
11011
11012        public void show() {
11013            if (isInBatchEditMode()) {
11014                return;
11015            }
11016            initDrawables();
11017            initHandles();
11018            hideInsertionPointCursorController();
11019        }
11020
11021        private void initDrawables() {
11022            if (mSelectHandleLeft == null) {
11023                mSelectHandleLeft = mContext.getResources().getDrawable(
11024                        mTextSelectHandleLeftRes);
11025            }
11026            if (mSelectHandleRight == null) {
11027                mSelectHandleRight = mContext.getResources().getDrawable(
11028                        mTextSelectHandleRightRes);
11029            }
11030        }
11031
11032        private void initHandles() {
11033            // Lazy object creation has to be done before updatePosition() is called.
11034            if (mStartHandle == null) {
11035                mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
11036            }
11037            if (mEndHandle == null) {
11038                mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
11039            }
11040
11041            mStartHandle.show();
11042            mEndHandle.show();
11043
11044            // Make sure both left and right handles share the same ActionPopupWindow (so that
11045            // moving any of the handles hides the action popup).
11046            mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION);
11047            mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
11048
11049            hideInsertionPointCursorController();
11050        }
11051
11052        public void hide() {
11053            if (mStartHandle != null) mStartHandle.hide();
11054            if (mEndHandle != null) mEndHandle.hide();
11055        }
11056
11057        public void onTouchEvent(MotionEvent event) {
11058            // This is done even when the View does not have focus, so that long presses can start
11059            // selection and tap can move cursor from this tap position.
11060            switch (event.getActionMasked()) {
11061                case MotionEvent.ACTION_DOWN:
11062                    final float x = event.getX();
11063                    final float y = event.getY();
11064
11065                    // Remember finger down position, to be able to start selection from there
11066                    mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
11067
11068                    // Double tap detection
11069                    if (mGestureStayedInTapRegion) {
11070                        long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
11071                        if (duration <= ViewConfiguration.getDoubleTapTimeout()) {
11072                            final float deltaX = x - mDownPositionX;
11073                            final float deltaY = y - mDownPositionY;
11074                            final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
11075
11076                            ViewConfiguration viewConfiguration = ViewConfiguration.get(
11077                                    TextView.this.getContext());
11078                            int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
11079                            boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop;
11080
11081                            if (stayedInArea && isPositionOnText(x, y)) {
11082                                startSelectionActionMode();
11083                                mDiscardNextActionUp = true;
11084                            }
11085                        }
11086                    }
11087
11088                    mDownPositionX = x;
11089                    mDownPositionY = y;
11090                    mGestureStayedInTapRegion = true;
11091                    break;
11092
11093                case MotionEvent.ACTION_POINTER_DOWN:
11094                case MotionEvent.ACTION_POINTER_UP:
11095                    // Handle multi-point gestures. Keep min and max offset positions.
11096                    // Only activated for devices that correctly handle multi-touch.
11097                    if (mContext.getPackageManager().hasSystemFeature(
11098                            PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
11099                        updateMinAndMaxOffsets(event);
11100                    }
11101                    break;
11102
11103                case MotionEvent.ACTION_MOVE:
11104                    if (mGestureStayedInTapRegion) {
11105                        final float deltaX = event.getX() - mDownPositionX;
11106                        final float deltaY = event.getY() - mDownPositionY;
11107                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
11108
11109                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
11110                                TextView.this.getContext());
11111                        int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
11112
11113                        if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
11114                            mGestureStayedInTapRegion = false;
11115                        }
11116                    }
11117                    break;
11118
11119                case MotionEvent.ACTION_UP:
11120                    mPreviousTapUpTime = SystemClock.uptimeMillis();
11121                    break;
11122            }
11123        }
11124
11125        /**
11126         * @param event
11127         */
11128        private void updateMinAndMaxOffsets(MotionEvent event) {
11129            int pointerCount = event.getPointerCount();
11130            for (int index = 0; index < pointerCount; index++) {
11131                int offset = getOffsetForPosition(event.getX(index), event.getY(index));
11132                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
11133                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
11134            }
11135        }
11136
11137        public int getMinTouchOffset() {
11138            return mMinTouchOffset;
11139        }
11140
11141        public int getMaxTouchOffset() {
11142            return mMaxTouchOffset;
11143        }
11144
11145        public void resetTouchOffsets() {
11146            mMinTouchOffset = mMaxTouchOffset = -1;
11147        }
11148
11149        /**
11150         * @return true iff this controller is currently used to move the selection start.
11151         */
11152        public boolean isSelectionStartDragged() {
11153            return mStartHandle != null && mStartHandle.isDragging();
11154        }
11155
11156        public void onTouchModeChanged(boolean isInTouchMode) {
11157            if (!isInTouchMode) {
11158                hide();
11159            }
11160        }
11161
11162        @Override
11163        public void onDetached() {
11164            final ViewTreeObserver observer = getViewTreeObserver();
11165            observer.removeOnTouchModeChangeListener(this);
11166
11167            if (mStartHandle != null) mStartHandle.onDetached();
11168            if (mEndHandle != null) mEndHandle.onDetached();
11169        }
11170    }
11171
11172    private void hideInsertionPointCursorController() {
11173        // No need to create the controller to hide it.
11174        if (mInsertionPointCursorController != null) {
11175            mInsertionPointCursorController.hide();
11176        }
11177    }
11178
11179    /**
11180     * Hides the insertion controller and stops text selection mode, hiding the selection controller
11181     */
11182    private void hideControllers() {
11183        hideCursorControllers();
11184        hideSpanControllers();
11185    }
11186
11187    private void hideSpanControllers() {
11188        if (mChangeWatcher != null) {
11189            mChangeWatcher.hideControllers();
11190        }
11191    }
11192
11193    private void hideCursorControllers() {
11194        if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) {
11195            // Should be done before hide insertion point controller since it triggers a show of it
11196            mSuggestionsPopupWindow.hide();
11197        }
11198        hideInsertionPointCursorController();
11199        stopSelectionActionMode();
11200    }
11201
11202    /**
11203     * Get the character offset closest to the specified absolute position. A typical use case is to
11204     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
11205     *
11206     * @param x The horizontal absolute position of a point on screen
11207     * @param y The vertical absolute position of a point on screen
11208     * @return the character offset for the character whose position is closest to the specified
11209     *  position. Returns -1 if there is no layout.
11210     */
11211    public int getOffsetForPosition(float x, float y) {
11212        if (getLayout() == null) return -1;
11213        final int line = getLineAtCoordinate(y);
11214        final int offset = getOffsetAtCoordinate(line, x);
11215        return offset;
11216    }
11217
11218    private float convertToLocalHorizontalCoordinate(float x) {
11219        x -= getTotalPaddingLeft();
11220        // Clamp the position to inside of the view.
11221        x = Math.max(0.0f, x);
11222        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
11223        x += getScrollX();
11224        return x;
11225    }
11226
11227    private int getLineAtCoordinate(float y) {
11228        y -= getTotalPaddingTop();
11229        // Clamp the position to inside of the view.
11230        y = Math.max(0.0f, y);
11231        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
11232        y += getScrollY();
11233        return getLayout().getLineForVertical((int) y);
11234    }
11235
11236    private int getOffsetAtCoordinate(int line, float x) {
11237        x = convertToLocalHorizontalCoordinate(x);
11238        return getLayout().getOffsetForHorizontal(line, x);
11239    }
11240
11241    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
11242     * in the view. Returns false when the position is in the empty space of left/right of text.
11243     */
11244    private boolean isPositionOnText(float x, float y) {
11245        if (getLayout() == null) return false;
11246
11247        final int line = getLineAtCoordinate(y);
11248        x = convertToLocalHorizontalCoordinate(x);
11249
11250        if (x < getLayout().getLineLeft(line)) return false;
11251        if (x > getLayout().getLineRight(line)) return false;
11252        return true;
11253    }
11254
11255    @Override
11256    public boolean onDragEvent(DragEvent event) {
11257        switch (event.getAction()) {
11258            case DragEvent.ACTION_DRAG_STARTED:
11259                return hasInsertionController();
11260
11261            case DragEvent.ACTION_DRAG_ENTERED:
11262                TextView.this.requestFocus();
11263                return true;
11264
11265            case DragEvent.ACTION_DRAG_LOCATION:
11266                final int offset = getOffsetForPosition(event.getX(), event.getY());
11267                Selection.setSelection((Spannable)mText, offset);
11268                return true;
11269
11270            case DragEvent.ACTION_DROP:
11271                onDrop(event);
11272                return true;
11273
11274            case DragEvent.ACTION_DRAG_ENDED:
11275            case DragEvent.ACTION_DRAG_EXITED:
11276            default:
11277                return true;
11278        }
11279    }
11280
11281    private void onDrop(DragEvent event) {
11282        StringBuilder content = new StringBuilder("");
11283        ClipData clipData = event.getClipData();
11284        final int itemCount = clipData.getItemCount();
11285        for (int i=0; i < itemCount; i++) {
11286            Item item = clipData.getItemAt(i);
11287            content.append(item.coerceToText(TextView.this.mContext));
11288        }
11289
11290        final int offset = getOffsetForPosition(event.getX(), event.getY());
11291
11292        Object localState = event.getLocalState();
11293        DragLocalState dragLocalState = null;
11294        if (localState instanceof DragLocalState) {
11295            dragLocalState = (DragLocalState) localState;
11296        }
11297        boolean dragDropIntoItself = dragLocalState != null &&
11298                dragLocalState.sourceTextView == this;
11299
11300        if (dragDropIntoItself) {
11301            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
11302                // A drop inside the original selection discards the drop.
11303                return;
11304            }
11305        }
11306
11307        final int originalLength = mText.length();
11308        long minMax = prepareSpacesAroundPaste(offset, offset, content);
11309        int min = extractRangeStartFromLong(minMax);
11310        int max = extractRangeEndFromLong(minMax);
11311
11312        Selection.setSelection((Spannable) mText, max);
11313        replaceText_internal(min, max, content);
11314
11315        if (dragDropIntoItself) {
11316            int dragSourceStart = dragLocalState.start;
11317            int dragSourceEnd = dragLocalState.end;
11318            if (max <= dragSourceStart) {
11319                // Inserting text before selection has shifted positions
11320                final int shift = mText.length() - originalLength;
11321                dragSourceStart += shift;
11322                dragSourceEnd += shift;
11323            }
11324
11325            // Delete original selection
11326            deleteText_internal(dragSourceStart, dragSourceEnd);
11327
11328            // Make sure we do not leave two adjacent spaces.
11329            if ((dragSourceStart == 0 ||
11330                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
11331                    (dragSourceStart == mText.length() ||
11332                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
11333                final int pos = dragSourceStart == mText.length() ?
11334                        dragSourceStart - 1 : dragSourceStart;
11335                deleteText_internal(pos, pos + 1);
11336            }
11337        }
11338    }
11339
11340    /**
11341     * @return True if this view supports insertion handles.
11342     */
11343    boolean hasInsertionController() {
11344        return mInsertionControllerEnabled;
11345    }
11346
11347    /**
11348     * @return True if this view supports selection handles.
11349     */
11350    boolean hasSelectionController() {
11351        return mSelectionControllerEnabled;
11352    }
11353
11354    InsertionPointCursorController getInsertionController() {
11355        if (!mInsertionControllerEnabled) {
11356            return null;
11357        }
11358
11359        if (mInsertionPointCursorController == null) {
11360            mInsertionPointCursorController = new InsertionPointCursorController();
11361
11362            final ViewTreeObserver observer = getViewTreeObserver();
11363            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
11364        }
11365
11366        return mInsertionPointCursorController;
11367    }
11368
11369    SelectionModifierCursorController getSelectionController() {
11370        if (!mSelectionControllerEnabled) {
11371            return null;
11372        }
11373
11374        if (mSelectionModifierCursorController == null) {
11375            mSelectionModifierCursorController = new SelectionModifierCursorController();
11376
11377            final ViewTreeObserver observer = getViewTreeObserver();
11378            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
11379        }
11380
11381        return mSelectionModifierCursorController;
11382    }
11383
11384    boolean isInBatchEditMode() {
11385        final InputMethodState ims = mInputMethodState;
11386        if (ims != null) {
11387            return ims.mBatchEditNesting > 0;
11388        }
11389        return mInBatchEditControllers;
11390    }
11391
11392    @Override
11393    protected void resolveTextDirection() {
11394        if (hasPasswordTransformationMethod()) {
11395            mTextDir = TextDirectionHeuristics.LOCALE;
11396            return;
11397        }
11398
11399        // Always need to resolve layout direction first
11400        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
11401
11402        // Then resolve text direction on the parent
11403        super.resolveTextDirection();
11404
11405        // Now, we can select the heuristic
11406        int textDir = getResolvedTextDirection();
11407        switch (textDir) {
11408            default:
11409            case TEXT_DIRECTION_FIRST_STRONG:
11410                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11411                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
11412                break;
11413            case TEXT_DIRECTION_ANY_RTL:
11414                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
11415                break;
11416            case TEXT_DIRECTION_LTR:
11417                mTextDir = TextDirectionHeuristics.LTR;
11418                break;
11419            case TEXT_DIRECTION_RTL:
11420                mTextDir = TextDirectionHeuristics.RTL;
11421                break;
11422            case TEXT_DIRECTION_LOCALE:
11423                mTextDir = TextDirectionHeuristics.LOCALE;
11424                break;
11425        }
11426    }
11427
11428    /**
11429     * Subclasses will need to override this method to implement their own way of resolving
11430     * drawables depending on the layout direction.
11431     *
11432     * A call to the super method will be required from the subclasses implementation.
11433     *
11434     */
11435    protected void resolveDrawables() {
11436        // No need to resolve twice
11437        if (mResolvedDrawables) {
11438            return;
11439        }
11440        // No drawable to resolve
11441        if (mDrawables == null) {
11442            return;
11443        }
11444        // No relative drawable to resolve
11445        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
11446            mResolvedDrawables = true;
11447            return;
11448        }
11449
11450        Drawables dr = mDrawables;
11451        switch(getResolvedLayoutDirection()) {
11452            case LAYOUT_DIRECTION_RTL:
11453                if (dr.mDrawableStart != null) {
11454                    dr.mDrawableRight = dr.mDrawableStart;
11455
11456                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
11457                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
11458                }
11459                if (dr.mDrawableEnd != null) {
11460                    dr.mDrawableLeft = dr.mDrawableEnd;
11461
11462                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
11463                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
11464                }
11465                break;
11466
11467            case LAYOUT_DIRECTION_LTR:
11468            default:
11469                if (dr.mDrawableStart != null) {
11470                    dr.mDrawableLeft = dr.mDrawableStart;
11471
11472                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
11473                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
11474                }
11475                if (dr.mDrawableEnd != null) {
11476                    dr.mDrawableRight = dr.mDrawableEnd;
11477
11478                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
11479                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
11480                }
11481                break;
11482        }
11483        mResolvedDrawables = true;
11484    }
11485
11486    protected void resetResolvedDrawables() {
11487        mResolvedDrawables = false;
11488    }
11489
11490    /**
11491     * @hide
11492     */
11493    protected void viewClicked(InputMethodManager imm) {
11494        if (imm != null) {
11495            imm.viewClicked(this);
11496        }
11497    }
11498
11499    /**
11500     * Deletes the range of text [start, end[.
11501     * @hide
11502     */
11503    protected void deleteText_internal(int start, int end) {
11504        ((Editable) mText).delete(start, end);
11505    }
11506
11507    /**
11508     * Replaces the range of text [start, end[ by replacement text
11509     * @hide
11510     */
11511    protected void replaceText_internal(int start, int end, CharSequence text) {
11512        ((Editable) mText).replace(start, end, text);
11513    }
11514
11515    /**
11516     * Sets a span on the specified range of text
11517     * @hide
11518     */
11519    protected void setSpan_internal(Object span, int start, int end, int flags) {
11520        ((Editable) mText).setSpan(span, start, end, flags);
11521    }
11522
11523    /**
11524     * Moves the cursor to the specified offset position in text
11525     * @hide
11526     */
11527    protected void setCursorPosition_internal(int start, int end) {
11528        Selection.setSelection(((Editable) mText), start, end);
11529    }
11530
11531    @ViewDebug.ExportedProperty(category = "text")
11532    private CharSequence            mText;
11533    private CharSequence            mTransformed;
11534    private BufferType              mBufferType = BufferType.NORMAL;
11535
11536    private int                     mInputType = EditorInfo.TYPE_NULL;
11537    private CharSequence            mHint;
11538    private Layout                  mHintLayout;
11539
11540    private KeyListener             mInput;
11541
11542    private MovementMethod          mMovement;
11543    private TransformationMethod    mTransformation;
11544    private boolean                 mAllowTransformationLengthChange;
11545    private ChangeWatcher           mChangeWatcher;
11546
11547    private ArrayList<TextWatcher>  mListeners = null;
11548
11549    // display attributes
11550    private final TextPaint         mTextPaint;
11551    private boolean                 mUserSetTextScaleX;
11552    private final Paint             mHighlightPaint;
11553    private int                     mHighlightColor = 0x6633B5E5;
11554    private Layout                  mLayout;
11555
11556    private long                    mShowCursor;
11557    private Blink                   mBlink;
11558    private boolean                 mCursorVisible = true;
11559
11560    // Cursor Controllers.
11561    private InsertionPointCursorController mInsertionPointCursorController;
11562    private SelectionModifierCursorController mSelectionModifierCursorController;
11563    private ActionMode              mSelectionActionMode;
11564    private boolean                 mInsertionControllerEnabled;
11565    private boolean                 mSelectionControllerEnabled;
11566    private boolean                 mInBatchEditControllers;
11567
11568    private boolean                 mSelectAllOnFocus = false;
11569
11570    private int                     mGravity = Gravity.TOP | Gravity.START;
11571    private boolean                 mHorizontallyScrolling;
11572
11573    private int                     mAutoLinkMask;
11574    private boolean                 mLinksClickable = true;
11575
11576    private float                   mSpacingMult = 1.0f;
11577    private float                   mSpacingAdd = 0.0f;
11578    private boolean                 mTextIsSelectable = false;
11579
11580    private static final int        LINES = 1;
11581    private static final int        EMS = LINES;
11582    private static final int        PIXELS = 2;
11583
11584    private int                     mMaximum = Integer.MAX_VALUE;
11585    private int                     mMaxMode = LINES;
11586    private int                     mMinimum = 0;
11587    private int                     mMinMode = LINES;
11588
11589    private int                     mOldMaximum = mMaximum;
11590    private int                     mOldMaxMode = mMaxMode;
11591
11592    private int                     mMaxWidth = Integer.MAX_VALUE;
11593    private int                     mMaxWidthMode = PIXELS;
11594    private int                     mMinWidth = 0;
11595    private int                     mMinWidthMode = PIXELS;
11596
11597    private boolean                 mSingleLine;
11598    private int                     mDesiredHeightAtMeasure = -1;
11599    private boolean                 mIncludePad = true;
11600
11601    // tmp primitives, so we don't alloc them on each draw
11602    private Path                    mHighlightPath;
11603    private boolean                 mHighlightPathBogus = true;
11604    private static final RectF      sTempRect = new RectF();
11605    private static final float[]    sTmpPosition = new float[2];
11606
11607    // XXX should be much larger
11608    private static final int        VERY_WIDE = 1024*1024;
11609
11610    private static final int        BLINK = 500;
11611
11612    private static final int ANIMATED_SCROLL_GAP = 250;
11613    private long mLastScroll;
11614    private Scroller mScroller = null;
11615
11616    private BoringLayout.Metrics mBoring;
11617    private BoringLayout.Metrics mHintBoring;
11618
11619    private BoringLayout mSavedLayout, mSavedHintLayout;
11620
11621    private TextDirectionHeuristic mTextDir = null;
11622
11623    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
11624    private InputFilter[] mFilters = NO_FILTERS;
11625    private static final Spanned EMPTY_SPANNED = new SpannedString("");
11626    private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
11627    // System wide time for last cut or copy action.
11628    private static long sLastCutOrCopyTime;
11629    // Used to highlight a word when it is corrected by the IME
11630    private CorrectionHighlighter mCorrectionHighlighter;
11631    // New state used to change background based on whether this TextView is multiline.
11632    private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
11633}
11634