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