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