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