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