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