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