TextView.java revision 57324c738272eb35217eebfa5001eff45101f683
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.SuggestionRangeSpan;
88import android.text.style.SuggestionSpan;
89import android.text.style.TextAppearanceSpan;
90import android.text.style.URLSpan;
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                sp.removeSpan(mSuggestionRangeSpan);
2898
2899                ss.text = sp;
2900            } else {
2901                ss.text = mText.toString();
2902            }
2903
2904            if (isFocused() && start >= 0 && end >= 0) {
2905                ss.frozenWithFocus = true;
2906            }
2907
2908            ss.error = mError;
2909
2910            return ss;
2911        }
2912
2913        return superState;
2914    }
2915
2916    @Override
2917    public void onRestoreInstanceState(Parcelable state) {
2918        if (!(state instanceof SavedState)) {
2919            super.onRestoreInstanceState(state);
2920            return;
2921        }
2922
2923        SavedState ss = (SavedState)state;
2924        super.onRestoreInstanceState(ss.getSuperState());
2925
2926        // XXX restore buffer type too, as well as lots of other stuff
2927        if (ss.text != null) {
2928            setText(ss.text);
2929        }
2930
2931        if (ss.selStart >= 0 && ss.selEnd >= 0) {
2932            if (mText instanceof Spannable) {
2933                int len = mText.length();
2934
2935                if (ss.selStart > len || ss.selEnd > len) {
2936                    String restored = "";
2937
2938                    if (ss.text != null) {
2939                        restored = "(restored) ";
2940                    }
2941
2942                    Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
2943                          "/" + ss.selEnd + " out of range for " + restored +
2944                          "text " + mText);
2945                } else {
2946                    Selection.setSelection((Spannable) mText, ss.selStart,
2947                                           ss.selEnd);
2948
2949                    if (ss.frozenWithFocus) {
2950                        mFrozenWithFocus = true;
2951                    }
2952                }
2953            }
2954        }
2955
2956        if (ss.error != null) {
2957            final CharSequence error = ss.error;
2958            // Display the error later, after the first layout pass
2959            post(new Runnable() {
2960                public void run() {
2961                    setError(error);
2962                }
2963            });
2964        }
2965    }
2966
2967    /**
2968     * Control whether this text view saves its entire text contents when
2969     * freezing to an icicle, in addition to dynamic state such as cursor
2970     * position.  By default this is false, not saving the text.  Set to true
2971     * if the text in the text view is not being saved somewhere else in
2972     * persistent storage (such as in a content provider) so that if the
2973     * view is later thawed the user will not lose their data.
2974     *
2975     * @param freezesText Controls whether a frozen icicle should include the
2976     * entire text data: true to include it, false to not.
2977     *
2978     * @attr ref android.R.styleable#TextView_freezesText
2979     */
2980    @android.view.RemotableViewMethod
2981    public void setFreezesText(boolean freezesText) {
2982        mFreezesText = freezesText;
2983    }
2984
2985    /**
2986     * Return whether this text view is including its entire text contents
2987     * in frozen icicles.
2988     *
2989     * @return Returns true if text is included, false if it isn't.
2990     *
2991     * @see #setFreezesText
2992     */
2993    public boolean getFreezesText() {
2994        return mFreezesText;
2995    }
2996
2997    ///////////////////////////////////////////////////////////////////////////
2998
2999    /**
3000     * Sets the Factory used to create new Editables.
3001     */
3002    public final void setEditableFactory(Editable.Factory factory) {
3003        mEditableFactory = factory;
3004        setText(mText);
3005    }
3006
3007    /**
3008     * Sets the Factory used to create new Spannables.
3009     */
3010    public final void setSpannableFactory(Spannable.Factory factory) {
3011        mSpannableFactory = factory;
3012        setText(mText);
3013    }
3014
3015    /**
3016     * Sets the string value of the TextView. TextView <em>does not</em> accept
3017     * HTML-like formatting, which you can do with text strings in XML resource files.
3018     * To style your strings, attach android.text.style.* objects to a
3019     * {@link android.text.SpannableString SpannableString}, or see the
3020     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
3021     * Available Resource Types</a> documentation for an example of setting
3022     * formatted text in the XML resource file.
3023     *
3024     * @attr ref android.R.styleable#TextView_text
3025     */
3026    @android.view.RemotableViewMethod
3027    public final void setText(CharSequence text) {
3028        setText(text, mBufferType);
3029    }
3030
3031    /**
3032     * Like {@link #setText(CharSequence)},
3033     * except that the cursor position (if any) is retained in the new text.
3034     *
3035     * @param text The new text to place in the text view.
3036     *
3037     * @see #setText(CharSequence)
3038     */
3039    @android.view.RemotableViewMethod
3040    public final void setTextKeepState(CharSequence text) {
3041        setTextKeepState(text, mBufferType);
3042    }
3043
3044    /**
3045     * Sets the text that this TextView is to display (see
3046     * {@link #setText(CharSequence)}) and also sets whether it is stored
3047     * in a styleable/spannable buffer and whether it is editable.
3048     *
3049     * @attr ref android.R.styleable#TextView_text
3050     * @attr ref android.R.styleable#TextView_bufferType
3051     */
3052    public void setText(CharSequence text, BufferType type) {
3053        setText(text, type, true, 0);
3054
3055        if (mCharWrapper != null) {
3056            mCharWrapper.mChars = null;
3057        }
3058    }
3059
3060    private void setText(CharSequence text, BufferType type,
3061                         boolean notifyBefore, int oldlen) {
3062        if (text == null) {
3063            text = "";
3064        }
3065
3066        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3067
3068        if (text instanceof Spanned &&
3069            ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
3070            setHorizontalFadingEdgeEnabled(
3071                    ViewConfiguration.get(mContext).isFadingMarqueeEnabled());
3072            setEllipsize(TextUtils.TruncateAt.MARQUEE);
3073        }
3074
3075        int n = mFilters.length;
3076        for (int i = 0; i < n; i++) {
3077            CharSequence out = mFilters[i].filter(text, 0, text.length(),
3078                                                  EMPTY_SPANNED, 0, 0);
3079            if (out != null) {
3080                text = out;
3081            }
3082        }
3083
3084        if (notifyBefore) {
3085            if (mText != null) {
3086                oldlen = mText.length();
3087                sendBeforeTextChanged(mText, 0, oldlen, text.length());
3088            } else {
3089                sendBeforeTextChanged("", 0, 0, text.length());
3090            }
3091        }
3092
3093        boolean needEditableForNotification = false;
3094        boolean startSpellCheck = false;
3095
3096        if (mListeners != null && mListeners.size() != 0) {
3097            needEditableForNotification = true;
3098        }
3099
3100        if (type == BufferType.EDITABLE || mInput != null || needEditableForNotification) {
3101            Editable t = mEditableFactory.newEditable(text);
3102            text = t;
3103            setFilters(t, mFilters);
3104            InputMethodManager imm = InputMethodManager.peekInstance();
3105            if (imm != null) imm.restartInput(this);
3106            startSpellCheck = true;
3107        } else if (type == BufferType.SPANNABLE || mMovement != null) {
3108            text = mSpannableFactory.newSpannable(text);
3109        } else if (!(text instanceof CharWrapper)) {
3110            text = TextUtils.stringOrSpannedString(text);
3111        }
3112
3113        if (mAutoLinkMask != 0) {
3114            Spannable s2;
3115
3116            if (type == BufferType.EDITABLE || text instanceof Spannable) {
3117                s2 = (Spannable) text;
3118            } else {
3119                s2 = mSpannableFactory.newSpannable(text);
3120            }
3121
3122            if (Linkify.addLinks(s2, mAutoLinkMask)) {
3123                text = s2;
3124                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3125
3126                /*
3127                 * We must go ahead and set the text before changing the
3128                 * movement method, because setMovementMethod() may call
3129                 * setText() again to try to upgrade the buffer type.
3130                 */
3131                mText = text;
3132
3133                // Do not change the movement method for text that support text selection as it
3134                // would prevent an arbitrary cursor displacement.
3135                if (mLinksClickable && !textCanBeSelected()) {
3136                    setMovementMethod(LinkMovementMethod.getInstance());
3137                }
3138            }
3139        }
3140
3141        mBufferType = type;
3142        mText = text;
3143
3144        if (mTransformation == null) {
3145            mTransformed = text;
3146        } else {
3147            mTransformed = mTransformation.getTransformation(text, this);
3148        }
3149
3150        final int textLength = text.length();
3151
3152        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
3153            Spannable sp = (Spannable) text;
3154
3155            // Remove any ChangeWatchers that might have come
3156            // from other TextViews.
3157            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3158            final int count = watchers.length;
3159            for (int i = 0; i < count; i++)
3160                sp.removeSpan(watchers[i]);
3161
3162            if (mChangeWatcher == null)
3163                mChangeWatcher = new ChangeWatcher();
3164
3165            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
3166                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
3167
3168            if (mInput != null) {
3169                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3170            }
3171
3172            if (mTransformation != null) {
3173                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3174            }
3175
3176            if (mMovement != null) {
3177                mMovement.initialize(this, (Spannable) text);
3178
3179                /*
3180                 * Initializing the movement method will have set the
3181                 * selection, so reset mSelectionMoved to keep that from
3182                 * interfering with the normal on-focus selection-setting.
3183                 */
3184                mSelectionMoved = false;
3185            }
3186        }
3187
3188        if (mLayout != null) {
3189            checkForRelayout();
3190        }
3191
3192        sendOnTextChanged(text, 0, oldlen, textLength);
3193        onTextChanged(text, 0, oldlen, textLength);
3194
3195        if (startSpellCheck) {
3196            updateSpellCheckSpans(0, textLength);
3197        }
3198
3199        if (needEditableForNotification) {
3200            sendAfterTextChanged((Editable) text);
3201        }
3202
3203        // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
3204        prepareCursorControllers();
3205    }
3206
3207    /**
3208     * Sets the TextView to display the specified slice of the specified
3209     * char array.  You must promise that you will not change the contents
3210     * of the array except for right before another call to setText(),
3211     * since the TextView has no way to know that the text
3212     * has changed and that it needs to invalidate and re-layout.
3213     */
3214    public final void setText(char[] text, int start, int len) {
3215        int oldlen = 0;
3216
3217        if (start < 0 || len < 0 || start + len > text.length) {
3218            throw new IndexOutOfBoundsException(start + ", " + len);
3219        }
3220
3221        /*
3222         * We must do the before-notification here ourselves because if
3223         * the old text is a CharWrapper we destroy it before calling
3224         * into the normal path.
3225         */
3226        if (mText != null) {
3227            oldlen = mText.length();
3228            sendBeforeTextChanged(mText, 0, oldlen, len);
3229        } else {
3230            sendBeforeTextChanged("", 0, 0, len);
3231        }
3232
3233        if (mCharWrapper == null) {
3234            mCharWrapper = new CharWrapper(text, start, len);
3235        } else {
3236            mCharWrapper.set(text, start, len);
3237        }
3238
3239        setText(mCharWrapper, mBufferType, false, oldlen);
3240    }
3241
3242    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
3243        private char[] mChars;
3244        private int mStart, mLength;
3245
3246        public CharWrapper(char[] chars, int start, int len) {
3247            mChars = chars;
3248            mStart = start;
3249            mLength = len;
3250        }
3251
3252        /* package */ void set(char[] chars, int start, int len) {
3253            mChars = chars;
3254            mStart = start;
3255            mLength = len;
3256        }
3257
3258        public int length() {
3259            return mLength;
3260        }
3261
3262        public char charAt(int off) {
3263            return mChars[off + mStart];
3264        }
3265
3266        @Override
3267        public String toString() {
3268            return new String(mChars, mStart, mLength);
3269        }
3270
3271        public CharSequence subSequence(int start, int end) {
3272            if (start < 0 || end < 0 || start > mLength || end > mLength) {
3273                throw new IndexOutOfBoundsException(start + ", " + end);
3274            }
3275
3276            return new String(mChars, start + mStart, end - start);
3277        }
3278
3279        public void getChars(int start, int end, char[] buf, int off) {
3280            if (start < 0 || end < 0 || start > mLength || end > mLength) {
3281                throw new IndexOutOfBoundsException(start + ", " + end);
3282            }
3283
3284            System.arraycopy(mChars, start + mStart, buf, off, end - start);
3285        }
3286
3287        public void drawText(Canvas c, int start, int end,
3288                             float x, float y, Paint p) {
3289            c.drawText(mChars, start + mStart, end - start, x, y, p);
3290        }
3291
3292        public void drawTextRun(Canvas c, int start, int end,
3293                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
3294            int count = end - start;
3295            int contextCount = contextEnd - contextStart;
3296            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
3297                    contextCount, x, y, flags, p);
3298        }
3299
3300        public float measureText(int start, int end, Paint p) {
3301            return p.measureText(mChars, start + mStart, end - start);
3302        }
3303
3304        public int getTextWidths(int start, int end, float[] widths, Paint p) {
3305            return p.getTextWidths(mChars, start + mStart, end - start, widths);
3306        }
3307
3308        public float getTextRunAdvances(int start, int end, int contextStart,
3309                int contextEnd, int flags, float[] advances, int advancesIndex,
3310                Paint p) {
3311            int count = end - start;
3312            int contextCount = contextEnd - contextStart;
3313            return p.getTextRunAdvances(mChars, start + mStart, count,
3314                    contextStart + mStart, contextCount, flags, advances,
3315                    advancesIndex);
3316        }
3317
3318        public float getTextRunAdvances(int start, int end, int contextStart,
3319                int contextEnd, int flags, float[] advances, int advancesIndex,
3320                Paint p, int reserved) {
3321            int count = end - start;
3322            int contextCount = contextEnd - contextStart;
3323            return p.getTextRunAdvances(mChars, start + mStart, count,
3324                    contextStart + mStart, contextCount, flags, advances,
3325                    advancesIndex, reserved);
3326        }
3327
3328        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
3329                int offset, int cursorOpt, Paint p) {
3330            int contextCount = contextEnd - contextStart;
3331            return p.getTextRunCursor(mChars, contextStart + mStart,
3332                    contextCount, flags, offset + mStart, cursorOpt);
3333        }
3334    }
3335
3336    /**
3337     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3338     * except that the cursor position (if any) is retained in the new text.
3339     *
3340     * @see #setText(CharSequence, android.widget.TextView.BufferType)
3341     */
3342    public final void setTextKeepState(CharSequence text, BufferType type) {
3343        int start = getSelectionStart();
3344        int end = getSelectionEnd();
3345        int len = text.length();
3346
3347        setText(text, type);
3348
3349        if (start >= 0 || end >= 0) {
3350            if (mText instanceof Spannable) {
3351                Selection.setSelection((Spannable) mText,
3352                                       Math.max(0, Math.min(start, len)),
3353                                       Math.max(0, Math.min(end, len)));
3354            }
3355        }
3356    }
3357
3358    @android.view.RemotableViewMethod
3359    public final void setText(int resid) {
3360        setText(getContext().getResources().getText(resid));
3361    }
3362
3363    public final void setText(int resid, BufferType type) {
3364        setText(getContext().getResources().getText(resid), type);
3365    }
3366
3367    /**
3368     * Sets the text to be displayed when the text of the TextView is empty.
3369     * Null means to use the normal empty text. The hint does not currently
3370     * participate in determining the size of the view.
3371     *
3372     * @attr ref android.R.styleable#TextView_hint
3373     */
3374    @android.view.RemotableViewMethod
3375    public final void setHint(CharSequence hint) {
3376        mHint = TextUtils.stringOrSpannedString(hint);
3377
3378        if (mLayout != null) {
3379            checkForRelayout();
3380        }
3381
3382        if (mText.length() == 0) {
3383            invalidate();
3384        }
3385    }
3386
3387    /**
3388     * Sets the text to be displayed when the text of the TextView is empty,
3389     * from a resource.
3390     *
3391     * @attr ref android.R.styleable#TextView_hint
3392     */
3393    @android.view.RemotableViewMethod
3394    public final void setHint(int resid) {
3395        setHint(getContext().getResources().getText(resid));
3396    }
3397
3398    /**
3399     * Returns the hint that is displayed when the text of the TextView
3400     * is empty.
3401     *
3402     * @attr ref android.R.styleable#TextView_hint
3403     */
3404    @ViewDebug.CapturedViewProperty
3405    public CharSequence getHint() {
3406        return mHint;
3407    }
3408
3409    private static boolean isMultilineInputType(int type) {
3410        return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3411            (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3412    }
3413
3414    /**
3415     * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3416     * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3417     * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
3418     * then a soft keyboard will not be displayed for this text view.
3419     *
3420     * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3421     * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3422     * type.
3423     *
3424     * @see #getInputType()
3425     * @see #setRawInputType(int)
3426     * @see android.text.InputType
3427     * @attr ref android.R.styleable#TextView_inputType
3428     */
3429    public void setInputType(int type) {
3430        final boolean wasPassword = isPasswordInputType(mInputType);
3431        final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
3432        setInputType(type, false);
3433        final boolean isPassword = isPasswordInputType(type);
3434        final boolean isVisiblePassword = isVisiblePasswordInputType(type);
3435        boolean forceUpdate = false;
3436        if (isPassword) {
3437            setTransformationMethod(PasswordTransformationMethod.getInstance());
3438            setTypefaceByIndex(MONOSPACE, 0);
3439        } else if (isVisiblePassword) {
3440            if (mTransformation == PasswordTransformationMethod.getInstance()) {
3441                forceUpdate = true;
3442            }
3443            setTypefaceByIndex(MONOSPACE, 0);
3444        } else if (wasPassword || wasVisiblePassword) {
3445            // not in password mode, clean up typeface and transformation
3446            setTypefaceByIndex(-1, -1);
3447            if (mTransformation == PasswordTransformationMethod.getInstance()) {
3448                forceUpdate = true;
3449            }
3450        }
3451
3452        boolean singleLine = !isMultilineInputType(type);
3453
3454        // We need to update the single line mode if it has changed or we
3455        // were previously in password mode.
3456        if (mSingleLine != singleLine || forceUpdate) {
3457            // Change single line mode, but only change the transformation if
3458            // we are not in password mode.
3459            applySingleLine(singleLine, !isPassword, true);
3460        }
3461
3462        InputMethodManager imm = InputMethodManager.peekInstance();
3463        if (imm != null) imm.restartInput(this);
3464    }
3465
3466    /**
3467     * It would be better to rely on the input type for everything. A password inputType should have
3468     * a password transformation. We should hence use isPasswordInputType instead of this method.
3469     *
3470     * We should:
3471     * - Call setInputType in setKeyListener instead of changing the input type directly (which
3472     * would install the correct transformation).
3473     * - Refuse the installation of a non-password transformation in setTransformation if the input
3474     * type is password.
3475     *
3476     * However, this is like this for legacy reasons and we cannot break existing apps. This method
3477     * is useful since it matches what the user can see (obfuscated text or not).
3478     *
3479     * @return true if the current transformation method is of the password type.
3480     */
3481    private boolean hasPasswordTransformationMethod() {
3482        return mTransformation instanceof PasswordTransformationMethod;
3483    }
3484
3485    private static boolean isPasswordInputType(int inputType) {
3486        final int variation =
3487                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
3488        return variation
3489                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
3490                || variation
3491                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
3492                || variation
3493                == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
3494    }
3495
3496    private static boolean isVisiblePasswordInputType(int inputType) {
3497        final int variation =
3498                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
3499        return variation
3500                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
3501    }
3502
3503    /**
3504     * Directly change the content type integer of the text view, without
3505     * modifying any other state.
3506     * @see #setInputType(int)
3507     * @see android.text.InputType
3508     * @attr ref android.R.styleable#TextView_inputType
3509     */
3510    public void setRawInputType(int type) {
3511        mInputType = type;
3512    }
3513
3514    private void setInputType(int type, boolean direct) {
3515        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
3516        KeyListener input;
3517        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
3518            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
3519            TextKeyListener.Capitalize cap;
3520            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
3521                cap = TextKeyListener.Capitalize.CHARACTERS;
3522            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
3523                cap = TextKeyListener.Capitalize.WORDS;
3524            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
3525                cap = TextKeyListener.Capitalize.SENTENCES;
3526            } else {
3527                cap = TextKeyListener.Capitalize.NONE;
3528            }
3529            input = TextKeyListener.getInstance(autotext, cap);
3530        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
3531            input = DigitsKeyListener.getInstance(
3532                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
3533                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
3534        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
3535            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
3536                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
3537                    input = DateKeyListener.getInstance();
3538                    break;
3539                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
3540                    input = TimeKeyListener.getInstance();
3541                    break;
3542                default:
3543                    input = DateTimeKeyListener.getInstance();
3544                    break;
3545            }
3546        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
3547            input = DialerKeyListener.getInstance();
3548        } else {
3549            input = TextKeyListener.getInstance();
3550        }
3551        setRawInputType(type);
3552        if (direct) mInput = input;
3553        else {
3554            setKeyListenerOnly(input);
3555        }
3556    }
3557
3558    /**
3559     * Get the type of the content.
3560     *
3561     * @see #setInputType(int)
3562     * @see android.text.InputType
3563     */
3564    public int getInputType() {
3565        return mInputType;
3566    }
3567
3568    /**
3569     * Change the editor type integer associated with the text view, which
3570     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
3571     * has focus.
3572     * @see #getImeOptions
3573     * @see android.view.inputmethod.EditorInfo
3574     * @attr ref android.R.styleable#TextView_imeOptions
3575     */
3576    public void setImeOptions(int imeOptions) {
3577        if (mInputContentType == null) {
3578            mInputContentType = new InputContentType();
3579        }
3580        mInputContentType.imeOptions = imeOptions;
3581    }
3582
3583    /**
3584     * Get the type of the IME editor.
3585     *
3586     * @see #setImeOptions(int)
3587     * @see android.view.inputmethod.EditorInfo
3588     */
3589    public int getImeOptions() {
3590        return mInputContentType != null
3591                ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
3592    }
3593
3594    /**
3595     * Change the custom IME action associated with the text view, which
3596     * will be reported to an IME with {@link EditorInfo#actionLabel}
3597     * and {@link EditorInfo#actionId} when it has focus.
3598     * @see #getImeActionLabel
3599     * @see #getImeActionId
3600     * @see android.view.inputmethod.EditorInfo
3601     * @attr ref android.R.styleable#TextView_imeActionLabel
3602     * @attr ref android.R.styleable#TextView_imeActionId
3603     */
3604    public void setImeActionLabel(CharSequence label, int actionId) {
3605        if (mInputContentType == null) {
3606            mInputContentType = new InputContentType();
3607        }
3608        mInputContentType.imeActionLabel = label;
3609        mInputContentType.imeActionId = actionId;
3610    }
3611
3612    /**
3613     * Get the IME action label previous set with {@link #setImeActionLabel}.
3614     *
3615     * @see #setImeActionLabel
3616     * @see android.view.inputmethod.EditorInfo
3617     */
3618    public CharSequence getImeActionLabel() {
3619        return mInputContentType != null
3620                ? mInputContentType.imeActionLabel : null;
3621    }
3622
3623    /**
3624     * Get the IME action ID previous set with {@link #setImeActionLabel}.
3625     *
3626     * @see #setImeActionLabel
3627     * @see android.view.inputmethod.EditorInfo
3628     */
3629    public int getImeActionId() {
3630        return mInputContentType != null
3631                ? mInputContentType.imeActionId : 0;
3632    }
3633
3634    /**
3635     * Set a special listener to be called when an action is performed
3636     * on the text view.  This will be called when the enter key is pressed,
3637     * or when an action supplied to the IME is selected by the user.  Setting
3638     * this means that the normal hard key event will not insert a newline
3639     * into the text view, even if it is multi-line; holding down the ALT
3640     * modifier will, however, allow the user to insert a newline character.
3641     */
3642    public void setOnEditorActionListener(OnEditorActionListener l) {
3643        if (mInputContentType == null) {
3644            mInputContentType = new InputContentType();
3645        }
3646        mInputContentType.onEditorActionListener = l;
3647    }
3648
3649    /**
3650     * Called when an attached input method calls
3651     * {@link InputConnection#performEditorAction(int)
3652     * InputConnection.performEditorAction()}
3653     * for this text view.  The default implementation will call your action
3654     * listener supplied to {@link #setOnEditorActionListener}, or perform
3655     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
3656     * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
3657     * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
3658     * EditorInfo.IME_ACTION_DONE}.
3659     *
3660     * <p>For backwards compatibility, if no IME options have been set and the
3661     * text view would not normally advance focus on enter, then
3662     * the NEXT and DONE actions received here will be turned into an enter
3663     * key down/up pair to go through the normal key handling.
3664     *
3665     * @param actionCode The code of the action being performed.
3666     *
3667     * @see #setOnEditorActionListener
3668     */
3669    public void onEditorAction(int actionCode) {
3670        final InputContentType ict = mInputContentType;
3671        if (ict != null) {
3672            if (ict.onEditorActionListener != null) {
3673                if (ict.onEditorActionListener.onEditorAction(this,
3674                        actionCode, null)) {
3675                    return;
3676                }
3677            }
3678
3679            // This is the handling for some default action.
3680            // Note that for backwards compatibility we don't do this
3681            // default handling if explicit ime options have not been given,
3682            // instead turning this into the normal enter key codes that an
3683            // app may be expecting.
3684            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3685                View v = focusSearch(FOCUS_DOWN);
3686                if (v != null) {
3687                    if (!v.requestFocus(FOCUS_DOWN)) {
3688                        throw new IllegalStateException("focus search returned a view " +
3689                                "that wasn't able to take focus!");
3690                    }
3691                }
3692                return;
3693
3694            } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
3695                View v = focusSearch(FOCUS_UP);
3696                if (v != null) {
3697                    if (!v.requestFocus(FOCUS_UP)) {
3698                        throw new IllegalStateException("focus search returned a view " +
3699                                "that wasn't able to take focus!");
3700                    }
3701                }
3702                return;
3703
3704            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3705                InputMethodManager imm = InputMethodManager.peekInstance();
3706                if (imm != null && imm.isActive(this)) {
3707                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
3708                }
3709                return;
3710            }
3711        }
3712
3713        Handler h = getHandler();
3714        if (h != null) {
3715            long eventTime = SystemClock.uptimeMillis();
3716            h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
3717                    new KeyEvent(eventTime, eventTime,
3718                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
3719                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
3720                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3721                    | KeyEvent.FLAG_EDITOR_ACTION)));
3722            h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
3723                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
3724                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
3725                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
3726                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3727                    | KeyEvent.FLAG_EDITOR_ACTION)));
3728        }
3729    }
3730
3731    /**
3732     * Set the private content type of the text, which is the
3733     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3734     * field that will be filled in when creating an input connection.
3735     *
3736     * @see #getPrivateImeOptions()
3737     * @see EditorInfo#privateImeOptions
3738     * @attr ref android.R.styleable#TextView_privateImeOptions
3739     */
3740    public void setPrivateImeOptions(String type) {
3741        if (mInputContentType == null) mInputContentType = new InputContentType();
3742        mInputContentType.privateImeOptions = type;
3743    }
3744
3745    /**
3746     * Get the private type of the content.
3747     *
3748     * @see #setPrivateImeOptions(String)
3749     * @see EditorInfo#privateImeOptions
3750     */
3751    public String getPrivateImeOptions() {
3752        return mInputContentType != null
3753                ? mInputContentType.privateImeOptions : null;
3754    }
3755
3756    /**
3757     * Set the extra input data of the text, which is the
3758     * {@link EditorInfo#extras TextBoxAttribute.extras}
3759     * Bundle that will be filled in when creating an input connection.  The
3760     * given integer is the resource ID of an XML resource holding an
3761     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3762     *
3763     * @see #getInputExtras(boolean)
3764     * @see EditorInfo#extras
3765     * @attr ref android.R.styleable#TextView_editorExtras
3766     */
3767    public void setInputExtras(int xmlResId)
3768            throws XmlPullParserException, IOException {
3769        XmlResourceParser parser = getResources().getXml(xmlResId);
3770        if (mInputContentType == null) mInputContentType = new InputContentType();
3771        mInputContentType.extras = new Bundle();
3772        getResources().parseBundleExtras(parser, mInputContentType.extras);
3773    }
3774
3775    /**
3776     * Retrieve the input extras currently associated with the text view, which
3777     * can be viewed as well as modified.
3778     *
3779     * @param create If true, the extras will be created if they don't already
3780     * exist.  Otherwise, null will be returned if none have been created.
3781     * @see #setInputExtras(int)
3782     * @see EditorInfo#extras
3783     * @attr ref android.R.styleable#TextView_editorExtras
3784     */
3785    public Bundle getInputExtras(boolean create) {
3786        if (mInputContentType == null) {
3787            if (!create) return null;
3788            mInputContentType = new InputContentType();
3789        }
3790        if (mInputContentType.extras == null) {
3791            if (!create) return null;
3792            mInputContentType.extras = new Bundle();
3793        }
3794        return mInputContentType.extras;
3795    }
3796
3797    /**
3798     * Returns the error message that was set to be displayed with
3799     * {@link #setError}, or <code>null</code> if no error was set
3800     * or if it the error was cleared by the widget after user input.
3801     */
3802    public CharSequence getError() {
3803        return mError;
3804    }
3805
3806    /**
3807     * Sets the right-hand compound drawable of the TextView to the "error"
3808     * icon and sets an error message that will be displayed in a popup when
3809     * the TextView has focus.  The icon and error message will be reset to
3810     * null when any key events cause changes to the TextView's text.  If the
3811     * <code>error</code> is <code>null</code>, the error message and icon
3812     * will be cleared.
3813     */
3814    @android.view.RemotableViewMethod
3815    public void setError(CharSequence error) {
3816        if (error == null) {
3817            setError(null, null);
3818        } else {
3819            Drawable dr = getContext().getResources().
3820                getDrawable(com.android.internal.R.drawable.indicator_input_error);
3821
3822            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3823            setError(error, dr);
3824        }
3825    }
3826
3827    /**
3828     * Sets the right-hand compound drawable of the TextView to the specified
3829     * icon and sets an error message that will be displayed in a popup when
3830     * the TextView has focus.  The icon and error message will be reset to
3831     * null when any key events cause changes to the TextView's text.  The
3832     * drawable must already have had {@link Drawable#setBounds} set on it.
3833     * If the <code>error</code> is <code>null</code>, the error message will
3834     * be cleared (and you should provide a <code>null</code> icon as well).
3835     */
3836    public void setError(CharSequence error, Drawable icon) {
3837        error = TextUtils.stringOrSpannedString(error);
3838
3839        mError = error;
3840        mErrorWasChanged = true;
3841        final Drawables dr = mDrawables;
3842        if (dr != null) {
3843            switch (getResolvedLayoutDirection()) {
3844                default:
3845                case LAYOUT_DIRECTION_LTR:
3846                    setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon,
3847                            dr.mDrawableBottom);
3848                    break;
3849                case LAYOUT_DIRECTION_RTL:
3850                    setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight,
3851                            dr.mDrawableBottom);
3852                    break;
3853            }
3854        } else {
3855            setCompoundDrawables(null, null, icon, null);
3856        }
3857
3858        if (error == null) {
3859            if (mPopup != null) {
3860                if (mPopup.isShowing()) {
3861                    mPopup.dismiss();
3862                }
3863
3864                mPopup = null;
3865            }
3866        } else {
3867            if (isFocused()) {
3868                showError();
3869            }
3870        }
3871    }
3872
3873    private void showError() {
3874        if (getWindowToken() == null) {
3875            mShowErrorAfterAttach = true;
3876            return;
3877        }
3878
3879        if (mPopup == null) {
3880            LayoutInflater inflater = LayoutInflater.from(getContext());
3881            final TextView err = (TextView) inflater.inflate(
3882                    com.android.internal.R.layout.textview_hint, null);
3883
3884            final float scale = getResources().getDisplayMetrics().density;
3885            mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
3886            mPopup.setFocusable(false);
3887            // The user is entering text, so the input method is needed.  We
3888            // don't want the popup to be displayed on top of it.
3889            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
3890        }
3891
3892        TextView tv = (TextView) mPopup.getContentView();
3893        chooseSize(mPopup, mError, tv);
3894        tv.setText(mError);
3895
3896        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
3897        mPopup.fixDirection(mPopup.isAboveAnchor());
3898    }
3899
3900    private static class ErrorPopup extends PopupWindow {
3901        private boolean mAbove = false;
3902        private final TextView mView;
3903        private int mPopupInlineErrorBackgroundId = 0;
3904        private int mPopupInlineErrorAboveBackgroundId = 0;
3905
3906        ErrorPopup(TextView v, int width, int height) {
3907            super(v, width, height);
3908            mView = v;
3909            // Make sure the TextView has a background set as it will be used the first time it is
3910            // shown and positionned. Initialized with below background, which should have
3911            // dimensions identical to the above version for this to work (and is more likely).
3912            mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
3913                    com.android.internal.R.styleable.Theme_errorMessageBackground);
3914            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
3915        }
3916
3917        void fixDirection(boolean above) {
3918            mAbove = above;
3919
3920            if (above) {
3921                mPopupInlineErrorAboveBackgroundId =
3922                    getResourceId(mPopupInlineErrorAboveBackgroundId,
3923                            com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
3924            } else {
3925                mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
3926                        com.android.internal.R.styleable.Theme_errorMessageBackground);
3927            }
3928
3929            mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
3930                mPopupInlineErrorBackgroundId);
3931        }
3932
3933        private int getResourceId(int currentId, int index) {
3934            if (currentId == 0) {
3935                TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
3936                        R.styleable.Theme);
3937                currentId = styledAttributes.getResourceId(index, 0);
3938                styledAttributes.recycle();
3939            }
3940            return currentId;
3941        }
3942
3943        @Override
3944        public void update(int x, int y, int w, int h, boolean force) {
3945            super.update(x, y, w, h, force);
3946
3947            boolean above = isAboveAnchor();
3948            if (above != mAbove) {
3949                fixDirection(above);
3950            }
3951        }
3952    }
3953
3954    /**
3955     * Returns the Y offset to make the pointy top of the error point
3956     * at the middle of the error icon.
3957     */
3958    private int getErrorX() {
3959        /*
3960         * The "25" is the distance between the point and the right edge
3961         * of the background
3962         */
3963        final float scale = getResources().getDisplayMetrics().density;
3964
3965        final Drawables dr = mDrawables;
3966        return getWidth() - mPopup.getWidth() - getPaddingRight() -
3967                (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
3968    }
3969
3970    /**
3971     * Returns the Y offset to make the pointy top of the error point
3972     * at the bottom of the error icon.
3973     */
3974    private int getErrorY() {
3975        /*
3976         * Compound, not extended, because the icon is not clipped
3977         * if the text height is smaller.
3978         */
3979        final int compoundPaddingTop = getCompoundPaddingTop();
3980        int vspace = mBottom - mTop - getCompoundPaddingBottom() - compoundPaddingTop;
3981
3982        final Drawables dr = mDrawables;
3983        int icontop = compoundPaddingTop +
3984                (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
3985
3986        /*
3987         * The "2" is the distance between the point and the top edge
3988         * of the background.
3989         */
3990        final float scale = getResources().getDisplayMetrics().density;
3991        return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - getHeight() -
3992                (int) (2 * scale + 0.5f);
3993    }
3994
3995    private void hideError() {
3996        if (mPopup != null) {
3997            if (mPopup.isShowing()) {
3998                mPopup.dismiss();
3999            }
4000        }
4001
4002        mShowErrorAfterAttach = false;
4003    }
4004
4005    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
4006        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
4007        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
4008
4009        /*
4010         * Figure out how big the text would be if we laid it out to the
4011         * full width of this view minus the border.
4012         */
4013        int cap = getWidth() - wid;
4014        if (cap < 0) {
4015            cap = 200; // We must not be measured yet -- setFrame() will fix it.
4016        }
4017
4018        Layout l = new StaticLayout(text, tv.getPaint(), cap,
4019                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
4020        float max = 0;
4021        for (int i = 0; i < l.getLineCount(); i++) {
4022            max = Math.max(max, l.getLineWidth(i));
4023        }
4024
4025        /*
4026         * Now set the popup size to be big enough for the text plus the border.
4027         */
4028        pop.setWidth(wid + (int) Math.ceil(max));
4029        pop.setHeight(ht + l.getHeight());
4030    }
4031
4032
4033    @Override
4034    protected boolean setFrame(int l, int t, int r, int b) {
4035        boolean result = super.setFrame(l, t, r, b);
4036
4037        if (mPopup != null) {
4038            TextView tv = (TextView) mPopup.getContentView();
4039            chooseSize(mPopup, mError, tv);
4040            mPopup.update(this, getErrorX(), getErrorY(),
4041                          mPopup.getWidth(), mPopup.getHeight());
4042        }
4043
4044        restartMarqueeIfNeeded();
4045
4046        return result;
4047    }
4048
4049    private void restartMarqueeIfNeeded() {
4050        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4051            mRestartMarquee = false;
4052            startMarquee();
4053        }
4054    }
4055
4056    /**
4057     * Sets the list of input filters that will be used if the buffer is
4058     * Editable.  Has no effect otherwise.
4059     *
4060     * @attr ref android.R.styleable#TextView_maxLength
4061     */
4062    public void setFilters(InputFilter[] filters) {
4063        if (filters == null) {
4064            throw new IllegalArgumentException();
4065        }
4066
4067        mFilters = filters;
4068
4069        if (mText instanceof Editable) {
4070            setFilters((Editable) mText, filters);
4071        }
4072    }
4073
4074    /**
4075     * Sets the list of input filters on the specified Editable,
4076     * and includes mInput in the list if it is an InputFilter.
4077     */
4078    private void setFilters(Editable e, InputFilter[] filters) {
4079        if (mInput instanceof InputFilter) {
4080            InputFilter[] nf = new InputFilter[filters.length + 1];
4081
4082            System.arraycopy(filters, 0, nf, 0, filters.length);
4083            nf[filters.length] = (InputFilter) mInput;
4084
4085            e.setFilters(nf);
4086        } else {
4087            e.setFilters(filters);
4088        }
4089    }
4090
4091    /**
4092     * Returns the current list of input filters.
4093     */
4094    public InputFilter[] getFilters() {
4095        return mFilters;
4096    }
4097
4098    /////////////////////////////////////////////////////////////////////////
4099
4100    private int getVerticalOffset(boolean forceNormal) {
4101        int voffset = 0;
4102        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4103
4104        Layout l = mLayout;
4105        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4106            l = mHintLayout;
4107        }
4108
4109        if (gravity != Gravity.TOP) {
4110            int boxht;
4111
4112            if (l == mHintLayout) {
4113                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
4114                        getCompoundPaddingBottom();
4115            } else {
4116                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
4117                        getExtendedPaddingBottom();
4118            }
4119            int textht = l.getHeight();
4120
4121            if (textht < boxht) {
4122                if (gravity == Gravity.BOTTOM)
4123                    voffset = boxht - textht;
4124                else // (gravity == Gravity.CENTER_VERTICAL)
4125                    voffset = (boxht - textht) >> 1;
4126            }
4127        }
4128        return voffset;
4129    }
4130
4131    private int getBottomVerticalOffset(boolean forceNormal) {
4132        int voffset = 0;
4133        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4134
4135        Layout l = mLayout;
4136        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4137            l = mHintLayout;
4138        }
4139
4140        if (gravity != Gravity.BOTTOM) {
4141            int boxht;
4142
4143            if (l == mHintLayout) {
4144                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
4145                        getCompoundPaddingBottom();
4146            } else {
4147                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
4148                        getExtendedPaddingBottom();
4149            }
4150            int textht = l.getHeight();
4151
4152            if (textht < boxht) {
4153                if (gravity == Gravity.TOP)
4154                    voffset = boxht - textht;
4155                else // (gravity == Gravity.CENTER_VERTICAL)
4156                    voffset = (boxht - textht) >> 1;
4157            }
4158        }
4159        return voffset;
4160    }
4161
4162    private void invalidateCursorPath() {
4163        if (mHighlightPathBogus) {
4164            invalidateCursor();
4165        } else {
4166            final int horizontalPadding = getCompoundPaddingLeft();
4167            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4168
4169            if (mCursorCount == 0) {
4170                synchronized (sTempRect) {
4171                    /*
4172                     * The reason for this concern about the thickness of the
4173                     * cursor and doing the floor/ceil on the coordinates is that
4174                     * some EditTexts (notably textfields in the Browser) have
4175                     * anti-aliased text where not all the characters are
4176                     * necessarily at integer-multiple locations.  This should
4177                     * make sure the entire cursor gets invalidated instead of
4178                     * sometimes missing half a pixel.
4179                     */
4180                    float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4181                    if (thick < 1.0f) {
4182                        thick = 1.0f;
4183                    }
4184
4185                    thick /= 2.0f;
4186
4187                    mHighlightPath.computeBounds(sTempRect, false);
4188
4189                    invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
4190                            (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),
4191                            (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
4192                            (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
4193                }
4194            } else {
4195                for (int i = 0; i < mCursorCount; i++) {
4196                    Rect bounds = mCursorDrawable[i].getBounds();
4197                    invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4198                            bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4199                }
4200            }
4201        }
4202    }
4203
4204    private void invalidateCursor() {
4205        int where = getSelectionEnd();
4206
4207        invalidateCursor(where, where, where);
4208    }
4209
4210    private void invalidateCursor(int a, int b, int c) {
4211        if (mLayout == null) {
4212            invalidate();
4213        } else {
4214            if (a >= 0 || b >= 0 || c >= 0) {
4215                int first = Math.min(Math.min(a, b), c);
4216                int last = Math.max(Math.max(a, b), c);
4217
4218                int line = mLayout.getLineForOffset(first);
4219                int top = mLayout.getLineTop(line);
4220
4221                // This is ridiculous, but the descent from the line above
4222                // can hang down into the line we really want to redraw,
4223                // so we have to invalidate part of the line above to make
4224                // sure everything that needs to be redrawn really is.
4225                // (But not the whole line above, because that would cause
4226                // the same problem with the descenders on the line above it!)
4227                if (line > 0) {
4228                    top -= mLayout.getLineDescent(line - 1);
4229                }
4230
4231                int line2;
4232
4233                if (first == last)
4234                    line2 = line;
4235                else
4236                    line2 = mLayout.getLineForOffset(last);
4237
4238                int bottom = mLayout.getLineTop(line2 + 1);
4239
4240                final int horizontalPadding = getCompoundPaddingLeft();
4241                final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4242
4243                // If used, the cursor drawables can have an arbitrary dimension that can go beyond
4244                // the invalidated lines specified above.
4245                for (int i = 0; i < mCursorCount; i++) {
4246                    Rect bounds = mCursorDrawable[i].getBounds();
4247                    top = Math.min(top, bounds.top);
4248                    bottom = Math.max(bottom, bounds.bottom);
4249                    // Horizontal bounds are already full width, no need to update
4250                }
4251
4252                invalidate(horizontalPadding + mScrollX, top + verticalPadding,
4253                        horizontalPadding + mScrollX + getWidth() -
4254                        getCompoundPaddingLeft() - getCompoundPaddingRight(),
4255                        bottom + verticalPadding);
4256            }
4257        }
4258    }
4259
4260    private void registerForPreDraw() {
4261        final ViewTreeObserver observer = getViewTreeObserver();
4262
4263        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
4264            observer.addOnPreDrawListener(this);
4265            mPreDrawState = PREDRAW_PENDING;
4266        } else if (mPreDrawState == PREDRAW_DONE) {
4267            mPreDrawState = PREDRAW_PENDING;
4268        }
4269
4270        // else state is PREDRAW_PENDING, so keep waiting.
4271    }
4272
4273    /**
4274     * {@inheritDoc}
4275     */
4276    public boolean onPreDraw() {
4277        if (mPreDrawState != PREDRAW_PENDING) {
4278            return true;
4279        }
4280
4281        if (mLayout == null) {
4282            assumeLayout();
4283        }
4284
4285        boolean changed = false;
4286
4287        if (mMovement != null) {
4288            /* This code also provides auto-scrolling when a cursor is moved using a
4289             * CursorController (insertion point or selection limits).
4290             * For selection, ensure start or end is visible depending on controller's state.
4291             */
4292            int curs = getSelectionEnd();
4293            // Do not create the controller if it is not already created.
4294            if (mSelectionModifierCursorController != null &&
4295                    mSelectionModifierCursorController.isSelectionStartDragged()) {
4296                curs = getSelectionStart();
4297            }
4298
4299            /*
4300             * TODO: This should really only keep the end in view if
4301             * it already was before the text changed.  I'm not sure
4302             * of a good way to tell from here if it was.
4303             */
4304            if (curs < 0 &&
4305                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
4306                curs = mText.length();
4307            }
4308
4309            if (curs >= 0) {
4310                changed = bringPointIntoView(curs);
4311            }
4312        } else {
4313            changed = bringTextIntoView();
4314        }
4315
4316        // This has to be checked here since:
4317        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4318        //   a screen rotation) since layout is not yet initialized at that point.
4319        if (mCreatedWithASelection) {
4320            startSelectionActionMode();
4321            mCreatedWithASelection = false;
4322        }
4323
4324        // Phone specific code (there is no ExtractEditText on tablets).
4325        // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4326        // not be set. Do the test here instead.
4327        if (this instanceof ExtractEditText && hasSelection()) {
4328            startSelectionActionMode();
4329        }
4330
4331        mPreDrawState = PREDRAW_DONE;
4332        return !changed;
4333    }
4334
4335    @Override
4336    protected void onAttachedToWindow() {
4337        super.onAttachedToWindow();
4338
4339        mTemporaryDetach = false;
4340
4341        if (mShowErrorAfterAttach) {
4342            showError();
4343            mShowErrorAfterAttach = false;
4344        }
4345
4346        final ViewTreeObserver observer = getViewTreeObserver();
4347        // No need to create the controller.
4348        // The get method will add the listener on controller creation.
4349        if (mInsertionPointCursorController != null) {
4350            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
4351        }
4352        if (mSelectionModifierCursorController != null) {
4353            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
4354        }
4355
4356        // Resolve drawables as the layout direction has been resolved
4357        resolveDrawables();
4358    }
4359
4360    @Override
4361    protected void onDetachedFromWindow() {
4362        super.onDetachedFromWindow();
4363
4364        final ViewTreeObserver observer = getViewTreeObserver();
4365        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
4366            observer.removeOnPreDrawListener(this);
4367            mPreDrawState = PREDRAW_NOT_REGISTERED;
4368        }
4369
4370        if (mError != null) {
4371            hideError();
4372        }
4373
4374        if (mBlink != null) {
4375            mBlink.removeCallbacks(mBlink);
4376        }
4377
4378        if (mInsertionPointCursorController != null) {
4379            mInsertionPointCursorController.onDetached();
4380        }
4381
4382        if (mSelectionModifierCursorController != null) {
4383            mSelectionModifierCursorController.onDetached();
4384        }
4385
4386        hideControllers();
4387
4388        resetResolvedDrawables();
4389    }
4390
4391    @Override
4392    protected boolean isPaddingOffsetRequired() {
4393        return mShadowRadius != 0 || mDrawables != null;
4394    }
4395
4396    @Override
4397    protected int getLeftPaddingOffset() {
4398        return getCompoundPaddingLeft() - mPaddingLeft +
4399                (int) Math.min(0, mShadowDx - mShadowRadius);
4400    }
4401
4402    @Override
4403    protected int getTopPaddingOffset() {
4404        return (int) Math.min(0, mShadowDy - mShadowRadius);
4405    }
4406
4407    @Override
4408    protected int getBottomPaddingOffset() {
4409        return (int) Math.max(0, mShadowDy + mShadowRadius);
4410    }
4411
4412    @Override
4413    protected int getRightPaddingOffset() {
4414        return -(getCompoundPaddingRight() - mPaddingRight) +
4415                (int) Math.max(0, mShadowDx + mShadowRadius);
4416    }
4417
4418    @Override
4419    protected boolean verifyDrawable(Drawable who) {
4420        final boolean verified = super.verifyDrawable(who);
4421        if (!verified && mDrawables != null) {
4422            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
4423                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4424                    who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
4425        }
4426        return verified;
4427    }
4428
4429    @Override
4430    public void jumpDrawablesToCurrentState() {
4431        super.jumpDrawablesToCurrentState();
4432        if (mDrawables != null) {
4433            if (mDrawables.mDrawableLeft != null) {
4434                mDrawables.mDrawableLeft.jumpToCurrentState();
4435            }
4436            if (mDrawables.mDrawableTop != null) {
4437                mDrawables.mDrawableTop.jumpToCurrentState();
4438            }
4439            if (mDrawables.mDrawableRight != null) {
4440                mDrawables.mDrawableRight.jumpToCurrentState();
4441            }
4442            if (mDrawables.mDrawableBottom != null) {
4443                mDrawables.mDrawableBottom.jumpToCurrentState();
4444            }
4445            if (mDrawables.mDrawableStart != null) {
4446                mDrawables.mDrawableStart.jumpToCurrentState();
4447            }
4448            if (mDrawables.mDrawableEnd != null) {
4449                mDrawables.mDrawableEnd.jumpToCurrentState();
4450            }
4451        }
4452    }
4453
4454    @Override
4455    public void invalidateDrawable(Drawable drawable) {
4456        if (verifyDrawable(drawable)) {
4457            final Rect dirty = drawable.getBounds();
4458            int scrollX = mScrollX;
4459            int scrollY = mScrollY;
4460
4461            // IMPORTANT: The coordinates below are based on the coordinates computed
4462            // for each compound drawable in onDraw(). Make sure to update each section
4463            // accordingly.
4464            final TextView.Drawables drawables = mDrawables;
4465            if (drawables != null) {
4466                if (drawable == drawables.mDrawableLeft) {
4467                    final int compoundPaddingTop = getCompoundPaddingTop();
4468                    final int compoundPaddingBottom = getCompoundPaddingBottom();
4469                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4470
4471                    scrollX += mPaddingLeft;
4472                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4473                } else if (drawable == drawables.mDrawableRight) {
4474                    final int compoundPaddingTop = getCompoundPaddingTop();
4475                    final int compoundPaddingBottom = getCompoundPaddingBottom();
4476                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
4477
4478                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4479                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4480                } else if (drawable == drawables.mDrawableTop) {
4481                    final int compoundPaddingLeft = getCompoundPaddingLeft();
4482                    final int compoundPaddingRight = getCompoundPaddingRight();
4483                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
4484
4485                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4486                    scrollY += mPaddingTop;
4487                } else if (drawable == drawables.mDrawableBottom) {
4488                    final int compoundPaddingLeft = getCompoundPaddingLeft();
4489                    final int compoundPaddingRight = getCompoundPaddingRight();
4490                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
4491
4492                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4493                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4494                }
4495            }
4496
4497            invalidate(dirty.left + scrollX, dirty.top + scrollY,
4498                    dirty.right + scrollX, dirty.bottom + scrollY);
4499        }
4500    }
4501
4502    /**
4503     * @hide
4504     */
4505    @Override
4506    public int getResolvedLayoutDirection(Drawable who) {
4507        if (who == null) return View.LAYOUT_DIRECTION_LTR;
4508        if (mDrawables != null) {
4509            final Drawables drawables = mDrawables;
4510            if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight ||
4511                who == drawables.mDrawableTop || who == drawables.mDrawableBottom ||
4512                who == drawables.mDrawableStart || who == drawables.mDrawableEnd) {
4513                return getResolvedLayoutDirection();
4514            }
4515        }
4516        return super.getResolvedLayoutDirection(who);
4517    }
4518
4519    @Override
4520    protected boolean onSetAlpha(int alpha) {
4521        // Alpha is supported if and only if the drawing can be done in one pass.
4522        // TODO text with spans with a background color currently do not respect this alpha.
4523        if (getBackground() == null) {
4524            mCurrentAlpha = alpha;
4525            final Drawables dr = mDrawables;
4526            if (dr != null) {
4527                if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
4528                if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
4529                if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
4530                if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
4531                if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha);
4532                if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha);
4533            }
4534            return true;
4535        }
4536
4537        mCurrentAlpha = 255;
4538        return false;
4539    }
4540
4541    /**
4542     * When a TextView is used to display a useful piece of information to the user (such as a
4543     * contact's address), it should be made selectable, so that the user can select and copy this
4544     * content.
4545     *
4546     * Use {@link #setTextIsSelectable(boolean)} or the
4547     * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
4548     * selectable (text is not selectable by default).
4549     *
4550     * Note that this method simply returns the state of this flag. Although this flag has to be set
4551     * in order to select text in non-editable TextView, the content of an {@link EditText} can
4552     * always be selected, independently of the value of this flag.
4553     *
4554     * @return True if the text displayed in this TextView can be selected by the user.
4555     *
4556     * @attr ref android.R.styleable#TextView_textIsSelectable
4557     */
4558    public boolean isTextSelectable() {
4559        return mTextIsSelectable;
4560    }
4561
4562    /**
4563     * Sets whether or not (default) the content of this view is selectable by the user.
4564     *
4565     * Note that this methods affect the {@link #setFocusable(boolean)},
4566     * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
4567     * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
4568     * customized.
4569     *
4570     * See {@link #isTextSelectable} for details.
4571     *
4572     * @param selectable Whether or not the content of this TextView should be selectable.
4573     */
4574    public void setTextIsSelectable(boolean selectable) {
4575        if (mTextIsSelectable == selectable) return;
4576
4577        mTextIsSelectable = selectable;
4578
4579        setFocusableInTouchMode(selectable);
4580        setFocusable(selectable);
4581        setClickable(selectable);
4582        setLongClickable(selectable);
4583
4584        // mInputType is already EditorInfo.TYPE_NULL and mInput is null;
4585
4586        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
4587        setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
4588
4589        // Called by setText above, but safer in case of future code changes
4590        prepareCursorControllers();
4591    }
4592
4593    @Override
4594    protected int[] onCreateDrawableState(int extraSpace) {
4595        final int[] drawableState;
4596
4597        if (mSingleLine) {
4598            drawableState = super.onCreateDrawableState(extraSpace);
4599        } else {
4600            drawableState = super.onCreateDrawableState(extraSpace + 1);
4601            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4602        }
4603
4604        if (mTextIsSelectable) {
4605            // Disable pressed state, which was introduced when TextView was made clickable.
4606            // Prevents text color change.
4607            // setClickable(false) would have a similar effect, but it also disables focus changes
4608            // and long press actions, which are both needed by text selection.
4609            final int length = drawableState.length;
4610            for (int i = 0; i < length; i++) {
4611                if (drawableState[i] == R.attr.state_pressed) {
4612                    final int[] nonPressedState = new int[length - 1];
4613                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4614                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4615                    return nonPressedState;
4616                }
4617            }
4618        }
4619
4620        return drawableState;
4621    }
4622
4623    @Override
4624    protected void onDraw(Canvas canvas) {
4625        if (mPreDrawState == PREDRAW_DONE) {
4626            final ViewTreeObserver observer = getViewTreeObserver();
4627            observer.removeOnPreDrawListener(this);
4628            mPreDrawState = PREDRAW_NOT_REGISTERED;
4629        }
4630
4631        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return;
4632
4633        restartMarqueeIfNeeded();
4634
4635        // Draw the background for this view
4636        super.onDraw(canvas);
4637
4638        final int compoundPaddingLeft = getCompoundPaddingLeft();
4639        final int compoundPaddingTop = getCompoundPaddingTop();
4640        final int compoundPaddingRight = getCompoundPaddingRight();
4641        final int compoundPaddingBottom = getCompoundPaddingBottom();
4642        final int scrollX = mScrollX;
4643        final int scrollY = mScrollY;
4644        final int right = mRight;
4645        final int left = mLeft;
4646        final int bottom = mBottom;
4647        final int top = mTop;
4648
4649        final Drawables dr = mDrawables;
4650        if (dr != null) {
4651            /*
4652             * Compound, not extended, because the icon is not clipped
4653             * if the text height is smaller.
4654             */
4655
4656            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4657            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4658
4659            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4660            // Make sure to update invalidateDrawable() when changing this code.
4661            if (dr.mDrawableLeft != null) {
4662                canvas.save();
4663                canvas.translate(scrollX + mPaddingLeft,
4664                                 scrollY + compoundPaddingTop +
4665                                 (vspace - dr.mDrawableHeightLeft) / 2);
4666                dr.mDrawableLeft.draw(canvas);
4667                canvas.restore();
4668            }
4669
4670            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4671            // Make sure to update invalidateDrawable() when changing this code.
4672            if (dr.mDrawableRight != null) {
4673                canvas.save();
4674                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
4675                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4676                dr.mDrawableRight.draw(canvas);
4677                canvas.restore();
4678            }
4679
4680            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4681            // Make sure to update invalidateDrawable() when changing this code.
4682            if (dr.mDrawableTop != null) {
4683                canvas.save();
4684                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
4685                        scrollY + mPaddingTop);
4686                dr.mDrawableTop.draw(canvas);
4687                canvas.restore();
4688            }
4689
4690            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4691            // Make sure to update invalidateDrawable() when changing this code.
4692            if (dr.mDrawableBottom != null) {
4693                canvas.save();
4694                canvas.translate(scrollX + compoundPaddingLeft +
4695                        (hspace - dr.mDrawableWidthBottom) / 2,
4696                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
4697                dr.mDrawableBottom.draw(canvas);
4698                canvas.restore();
4699            }
4700        }
4701
4702        int color = mCurTextColor;
4703
4704        if (mLayout == null) {
4705            assumeLayout();
4706        }
4707
4708        Layout layout = mLayout;
4709        int cursorcolor = color;
4710
4711        if (mHint != null && mText.length() == 0) {
4712            if (mHintTextColor != null) {
4713                color = mCurHintTextColor;
4714            }
4715
4716            layout = mHintLayout;
4717        }
4718
4719        mTextPaint.setColor(color);
4720        if (mCurrentAlpha != 255) {
4721            // If set, the alpha will override the color's alpha. Multiply the alphas.
4722            mTextPaint.setAlpha((mCurrentAlpha * Color.alpha(color)) / 255);
4723        }
4724        mTextPaint.drawableState = getDrawableState();
4725
4726        canvas.save();
4727        /*  Would be faster if we didn't have to do this. Can we chop the
4728            (displayable) text so that we don't need to do this ever?
4729        */
4730
4731        int extendedPaddingTop = getExtendedPaddingTop();
4732        int extendedPaddingBottom = getExtendedPaddingBottom();
4733
4734        float clipLeft = compoundPaddingLeft + scrollX;
4735        float clipTop = extendedPaddingTop + scrollY;
4736        float clipRight = right - left - compoundPaddingRight + scrollX;
4737        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
4738
4739        if (mShadowRadius != 0) {
4740            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
4741            clipRight += Math.max(0, mShadowDx + mShadowRadius);
4742
4743            clipTop += Math.min(0, mShadowDy - mShadowRadius);
4744            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
4745        }
4746
4747        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4748
4749        int voffsetText = 0;
4750        int voffsetCursor = 0;
4751
4752        // translate in by our padding
4753        {
4754            /* shortcircuit calling getVerticaOffset() */
4755            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4756                voffsetText = getVerticalOffset(false);
4757                voffsetCursor = getVerticalOffset(true);
4758            }
4759            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
4760        }
4761
4762        final int layoutDirection = getResolvedLayoutDirection();
4763        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
4764        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4765            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
4766                    (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
4767                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
4768                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
4769            }
4770
4771            if (mMarquee != null && mMarquee.isRunning()) {
4772                canvas.translate(-mMarquee.mScroll, 0.0f);
4773            }
4774        }
4775
4776        Path highlight = null;
4777        int selStart = -1, selEnd = -1;
4778        boolean drawCursor = false;
4779
4780        //  If there is no movement method, then there can be no selection.
4781        //  Check that first and attempt to skip everything having to do with
4782        //  the cursor.
4783        //  XXX This is not strictly true -- a program could set the
4784        //  selection manually if it really wanted to.
4785        if (mMovement != null && (isFocused() || isPressed())) {
4786            selStart = getSelectionStart();
4787            selEnd = getSelectionEnd();
4788
4789            if (selStart >= 0) {
4790                if (mHighlightPath == null) mHighlightPath = new Path();
4791
4792                if (selStart == selEnd) {
4793                    if (isCursorVisible() &&
4794                            (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
4795                        if (mHighlightPathBogus) {
4796                            mHighlightPath.reset();
4797                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
4798                            updateCursorsPositions();
4799                            mHighlightPathBogus = false;
4800                        }
4801
4802                        // XXX should pass to skin instead of drawing directly
4803                        mHighlightPaint.setColor(cursorcolor);
4804                        if (mCurrentAlpha != 255) {
4805                            mHighlightPaint.setAlpha(
4806                                    (mCurrentAlpha * Color.alpha(cursorcolor)) / 255);
4807                        }
4808                        mHighlightPaint.setStyle(Paint.Style.STROKE);
4809                        highlight = mHighlightPath;
4810                        drawCursor = mCursorCount > 0;
4811                    }
4812                } else if (textCanBeSelected()) {
4813                    if (mHighlightPathBogus) {
4814                        mHighlightPath.reset();
4815                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4816                        mHighlightPathBogus = false;
4817                    }
4818
4819                    // XXX should pass to skin instead of drawing directly
4820                    mHighlightPaint.setColor(mHighlightColor);
4821                    if (mCurrentAlpha != 255) {
4822                        mHighlightPaint.setAlpha(
4823                                (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
4824                    }
4825                    mHighlightPaint.setStyle(Paint.Style.FILL);
4826
4827                    highlight = mHighlightPath;
4828                }
4829            }
4830        }
4831
4832        /*  Comment out until we decide what to do about animations
4833        boolean isLinearTextOn = false;
4834        if (currentTransformation != null) {
4835            isLinearTextOn = mTextPaint.isLinearTextOn();
4836            Matrix m = currentTransformation.getMatrix();
4837            if (!m.isIdentity()) {
4838                // mTextPaint.setLinearTextOn(true);
4839            }
4840        }
4841        */
4842
4843        final InputMethodState ims = mInputMethodState;
4844        final int cursorOffsetVertical = voffsetCursor - voffsetText;
4845        if (ims != null && ims.mBatchEditNesting == 0) {
4846            InputMethodManager imm = InputMethodManager.peekInstance();
4847            if (imm != null) {
4848                if (imm.isActive(this)) {
4849                    boolean reported = false;
4850                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
4851                        // We are in extract mode and the content has changed
4852                        // in some way... just report complete new text to the
4853                        // input method.
4854                        reported = reportExtractedText();
4855                    }
4856                    if (!reported && highlight != null) {
4857                        int candStart = -1;
4858                        int candEnd = -1;
4859                        if (mText instanceof Spannable) {
4860                            Spannable sp = (Spannable)mText;
4861                            candStart = EditableInputConnection.getComposingSpanStart(sp);
4862                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4863                        }
4864                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4865                    }
4866                }
4867
4868                if (imm.isWatchingCursor(this) && highlight != null) {
4869                    highlight.computeBounds(ims.mTmpRectF, true);
4870                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4871
4872                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
4873                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4874
4875                    ims.mTmpRectF.offset(0, cursorOffsetVertical);
4876
4877                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4878                            (int)(ims.mTmpRectF.top + 0.5),
4879                            (int)(ims.mTmpRectF.right + 0.5),
4880                            (int)(ims.mTmpRectF.bottom + 0.5));
4881
4882                    imm.updateCursor(this,
4883                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4884                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4885                }
4886            }
4887        }
4888
4889        if (mCorrectionHighlighter != null) {
4890            mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
4891        }
4892
4893        if (drawCursor) {
4894            drawCursor(canvas, cursorOffsetVertical);
4895            // Rely on the drawable entirely, do not draw the cursor line.
4896            // Has to be done after the IMM related code above which relies on the highlight.
4897            highlight = null;
4898        }
4899
4900        layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
4901
4902        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
4903            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
4904            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
4905        }
4906
4907        /*  Comment out until we decide what to do about animations
4908        if (currentTransformation != null) {
4909            mTextPaint.setLinearTextOn(isLinearTextOn);
4910        }
4911        */
4912
4913        canvas.restore();
4914    }
4915
4916    private void updateCursorsPositions() {
4917        if (mCursorDrawableRes == 0) {
4918            mCursorCount = 0;
4919            return;
4920        }
4921
4922        final int offset = getSelectionStart();
4923        final int line = mLayout.getLineForOffset(offset);
4924        final int top = mLayout.getLineTop(line);
4925        final int bottom = mLayout.getLineTop(line + 1);
4926
4927        mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
4928
4929        int middle = bottom;
4930        if (mCursorCount == 2) {
4931            // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
4932            middle = (top + bottom) >> 1;
4933        }
4934
4935        updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
4936
4937        if (mCursorCount == 2) {
4938            updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
4939        }
4940    }
4941
4942    private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
4943        if (mCursorDrawable[cursorIndex] == null)
4944            mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
4945
4946        if (mTempRect == null) mTempRect = new Rect();
4947
4948        mCursorDrawable[cursorIndex].getPadding(mTempRect);
4949        final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
4950        horizontal = Math.max(0.5f, horizontal - 0.5f);
4951        final int left = (int) (horizontal) - mTempRect.left;
4952        mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
4953                bottom + mTempRect.bottom);
4954    }
4955
4956    private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
4957        final boolean translate = cursorOffsetVertical != 0;
4958        if (translate) canvas.translate(0, cursorOffsetVertical);
4959        for (int i = 0; i < mCursorCount; i++) {
4960            mCursorDrawable[i].draw(canvas);
4961        }
4962        if (translate) canvas.translate(0, -cursorOffsetVertical);
4963    }
4964
4965    @Override
4966    public void getFocusedRect(Rect r) {
4967        if (mLayout == null) {
4968            super.getFocusedRect(r);
4969            return;
4970        }
4971
4972        int selEnd = getSelectionEnd();
4973        if (selEnd < 0) {
4974            super.getFocusedRect(r);
4975            return;
4976        }
4977
4978        int selStart = getSelectionStart();
4979        if (selStart < 0 || selStart >= selEnd) {
4980            int line = mLayout.getLineForOffset(selEnd);
4981            r.top = mLayout.getLineTop(line);
4982            r.bottom = mLayout.getLineBottom(line);
4983            r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
4984            r.right = r.left + 4;
4985        } else {
4986            int lineStart = mLayout.getLineForOffset(selStart);
4987            int lineEnd = mLayout.getLineForOffset(selEnd);
4988            r.top = mLayout.getLineTop(lineStart);
4989            r.bottom = mLayout.getLineBottom(lineEnd);
4990            if (lineStart == lineEnd) {
4991                r.left = (int) mLayout.getPrimaryHorizontal(selStart);
4992                r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
4993            } else {
4994                // Selection extends across multiple lines -- the focused
4995                // rect covers the entire width.
4996                if (mHighlightPath == null) mHighlightPath = new Path();
4997                if (mHighlightPathBogus) {
4998                    mHighlightPath.reset();
4999                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5000                    mHighlightPathBogus = false;
5001                }
5002                synchronized (sTempRect) {
5003                    mHighlightPath.computeBounds(sTempRect, true);
5004                    r.left = (int)sTempRect.left-1;
5005                    r.right = (int)sTempRect.right+1;
5006                }
5007            }
5008        }
5009
5010        // Adjust for padding and gravity.
5011        int paddingLeft = getCompoundPaddingLeft();
5012        int paddingTop = getExtendedPaddingTop();
5013        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5014            paddingTop += getVerticalOffset(false);
5015        }
5016        r.offset(paddingLeft, paddingTop);
5017    }
5018
5019    /**
5020     * Return the number of lines of text, or 0 if the internal Layout has not
5021     * been built.
5022     */
5023    public int getLineCount() {
5024        return mLayout != null ? mLayout.getLineCount() : 0;
5025    }
5026
5027    /**
5028     * Return the baseline for the specified line (0...getLineCount() - 1)
5029     * If bounds is not null, return the top, left, right, bottom extents
5030     * of the specified line in it. If the internal Layout has not been built,
5031     * return 0 and set bounds to (0, 0, 0, 0)
5032     * @param line which line to examine (0..getLineCount() - 1)
5033     * @param bounds Optional. If not null, it returns the extent of the line
5034     * @return the Y-coordinate of the baseline
5035     */
5036    public int getLineBounds(int line, Rect bounds) {
5037        if (mLayout == null) {
5038            if (bounds != null) {
5039                bounds.set(0, 0, 0, 0);
5040            }
5041            return 0;
5042        }
5043        else {
5044            int baseline = mLayout.getLineBounds(line, bounds);
5045
5046            int voffset = getExtendedPaddingTop();
5047            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5048                voffset += getVerticalOffset(true);
5049            }
5050            if (bounds != null) {
5051                bounds.offset(getCompoundPaddingLeft(), voffset);
5052            }
5053            return baseline + voffset;
5054        }
5055    }
5056
5057    @Override
5058    public int getBaseline() {
5059        if (mLayout == null) {
5060            return super.getBaseline();
5061        }
5062
5063        int voffset = 0;
5064        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5065            voffset = getVerticalOffset(true);
5066        }
5067
5068        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5069    }
5070
5071    /**
5072     * @hide
5073     * @param offsetRequired
5074     */
5075    @Override
5076    protected int getFadeTop(boolean offsetRequired) {
5077        if (mLayout == null) return 0;
5078
5079        int voffset = 0;
5080        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5081            voffset = getVerticalOffset(true);
5082        }
5083
5084        if (offsetRequired) voffset += getTopPaddingOffset();
5085
5086        return getExtendedPaddingTop() + voffset;
5087    }
5088
5089    /**
5090     * @hide
5091     * @param offsetRequired
5092     */
5093    @Override
5094    protected int getFadeHeight(boolean offsetRequired) {
5095        return mLayout != null ? mLayout.getHeight() : 0;
5096    }
5097
5098    @Override
5099    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5100        if (keyCode == KeyEvent.KEYCODE_BACK) {
5101            boolean isInSelectionMode = mSelectionActionMode != null;
5102
5103            if (isInSelectionMode) {
5104                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5105                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5106                    if (state != null) {
5107                        state.startTracking(event, this);
5108                    }
5109                    return true;
5110                } else if (event.getAction() == KeyEvent.ACTION_UP) {
5111                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5112                    if (state != null) {
5113                        state.handleUpEvent(event);
5114                    }
5115                    if (event.isTracking() && !event.isCanceled()) {
5116                        if (isInSelectionMode) {
5117                            stopSelectionActionMode();
5118                            return true;
5119                        }
5120                    }
5121                }
5122            }
5123        }
5124        return super.onKeyPreIme(keyCode, event);
5125    }
5126
5127    @Override
5128    public boolean onKeyDown(int keyCode, KeyEvent event) {
5129        int which = doKeyDown(keyCode, event, null);
5130        if (which == 0) {
5131            // Go through default dispatching.
5132            return super.onKeyDown(keyCode, event);
5133        }
5134
5135        return true;
5136    }
5137
5138    @Override
5139    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5140        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5141
5142        int which = doKeyDown(keyCode, down, event);
5143        if (which == 0) {
5144            // Go through default dispatching.
5145            return super.onKeyMultiple(keyCode, repeatCount, event);
5146        }
5147        if (which == -1) {
5148            // Consumed the whole thing.
5149            return true;
5150        }
5151
5152        repeatCount--;
5153
5154        // We are going to dispatch the remaining events to either the input
5155        // or movement method.  To do this, we will just send a repeated stream
5156        // of down and up events until we have done the complete repeatCount.
5157        // It would be nice if those interfaces had an onKeyMultiple() method,
5158        // but adding that is a more complicated change.
5159        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5160        if (which == 1) {
5161            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
5162            while (--repeatCount > 0) {
5163                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
5164                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
5165            }
5166            hideErrorIfUnchanged();
5167
5168        } else if (which == 2) {
5169            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5170            while (--repeatCount > 0) {
5171                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5172                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5173            }
5174        }
5175
5176        return true;
5177    }
5178
5179    /**
5180     * Returns true if pressing ENTER in this field advances focus instead
5181     * of inserting the character.  This is true mostly in single-line fields,
5182     * but also in mail addresses and subjects which will display on multiple
5183     * lines but where it doesn't make sense to insert newlines.
5184     */
5185    private boolean shouldAdvanceFocusOnEnter() {
5186        if (mInput == null) {
5187            return false;
5188        }
5189
5190        if (mSingleLine) {
5191            return true;
5192        }
5193
5194        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5195            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
5196            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5197                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5198                return true;
5199            }
5200        }
5201
5202        return false;
5203    }
5204
5205    /**
5206     * Returns true if pressing TAB in this field advances focus instead
5207     * of inserting the character.  Insert tabs only in multi-line editors.
5208     */
5209    private boolean shouldAdvanceFocusOnTab() {
5210        if (mInput != null && !mSingleLine) {
5211            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5212                int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
5213                if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5214                        || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5215                    return false;
5216                }
5217            }
5218        }
5219        return true;
5220    }
5221
5222    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5223        if (!isEnabled()) {
5224            return 0;
5225        }
5226
5227        switch (keyCode) {
5228            case KeyEvent.KEYCODE_ENTER:
5229                mEnterKeyIsDown = true;
5230                if (event.hasNoModifiers()) {
5231                    // When mInputContentType is set, we know that we are
5232                    // running in a "modern" cupcake environment, so don't need
5233                    // to worry about the application trying to capture
5234                    // enter key events.
5235                    if (mInputContentType != null) {
5236                        // If there is an action listener, given them a
5237                        // chance to consume the event.
5238                        if (mInputContentType.onEditorActionListener != null &&
5239                                mInputContentType.onEditorActionListener.onEditorAction(
5240                                this, EditorInfo.IME_NULL, event)) {
5241                            mInputContentType.enterDown = true;
5242                            // We are consuming the enter key for them.
5243                            return -1;
5244                        }
5245                    }
5246
5247                    // If our editor should move focus when enter is pressed, or
5248                    // this is a generated event from an IME action button, then
5249                    // don't let it be inserted into the text.
5250                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5251                            || shouldAdvanceFocusOnEnter()) {
5252                        if (mOnClickListener != null) {
5253                            return 0;
5254                        }
5255                        return -1;
5256                    }
5257                }
5258                break;
5259
5260            case KeyEvent.KEYCODE_DPAD_CENTER:
5261                mDPadCenterIsDown = true;
5262                if (event.hasNoModifiers()) {
5263                    if (shouldAdvanceFocusOnEnter()) {
5264                        return 0;
5265                    }
5266                }
5267                break;
5268
5269            case KeyEvent.KEYCODE_TAB:
5270                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5271                    if (shouldAdvanceFocusOnTab()) {
5272                        return 0;
5273                    }
5274                }
5275                break;
5276
5277                // Has to be done on key down (and not on key up) to correctly be intercepted.
5278            case KeyEvent.KEYCODE_BACK:
5279                if (mSelectionActionMode != null) {
5280                    stopSelectionActionMode();
5281                    return -1;
5282                }
5283                break;
5284        }
5285
5286        if (mInput != null) {
5287            resetErrorChangedFlag();
5288
5289            boolean doDown = true;
5290            if (otherEvent != null) {
5291                try {
5292                    beginBatchEdit();
5293                    final boolean handled = mInput.onKeyOther(this, (Editable) mText, otherEvent);
5294                    hideErrorIfUnchanged();
5295                    doDown = false;
5296                    if (handled) {
5297                        return -1;
5298                    }
5299                } catch (AbstractMethodError e) {
5300                    // onKeyOther was added after 1.0, so if it isn't
5301                    // implemented we need to try to dispatch as a regular down.
5302                } finally {
5303                    endBatchEdit();
5304                }
5305            }
5306
5307            if (doDown) {
5308                beginBatchEdit();
5309                final boolean handled = mInput.onKeyDown(this, (Editable) mText, keyCode, event);
5310                endBatchEdit();
5311                hideErrorIfUnchanged();
5312                if (handled) return 1;
5313            }
5314        }
5315
5316        // bug 650865: sometimes we get a key event before a layout.
5317        // don't try to move around if we don't know the layout.
5318
5319        if (mMovement != null && mLayout != null) {
5320            boolean doDown = true;
5321            if (otherEvent != null) {
5322                try {
5323                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5324                            otherEvent);
5325                    doDown = false;
5326                    if (handled) {
5327                        return -1;
5328                    }
5329                } catch (AbstractMethodError e) {
5330                    // onKeyOther was added after 1.0, so if it isn't
5331                    // implemented we need to try to dispatch as a regular down.
5332                }
5333            }
5334            if (doDown) {
5335                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
5336                    return 2;
5337            }
5338        }
5339
5340        return 0;
5341    }
5342
5343    /**
5344     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5345     * can be recorded.
5346     * @hide
5347     */
5348    public void resetErrorChangedFlag() {
5349        /*
5350         * Keep track of what the error was before doing the input
5351         * so that if an input filter changed the error, we leave
5352         * that error showing.  Otherwise, we take down whatever
5353         * error was showing when the user types something.
5354         */
5355        mErrorWasChanged = false;
5356    }
5357
5358    /**
5359     * @hide
5360     */
5361    public void hideErrorIfUnchanged() {
5362        if (mError != null && !mErrorWasChanged) {
5363            setError(null, null);
5364        }
5365    }
5366
5367    @Override
5368    public boolean onKeyUp(int keyCode, KeyEvent event) {
5369        if (!isEnabled()) {
5370            return super.onKeyUp(keyCode, event);
5371        }
5372
5373        switch (keyCode) {
5374            case KeyEvent.KEYCODE_DPAD_CENTER:
5375                mDPadCenterIsDown = false;
5376                if (event.hasNoModifiers()) {
5377                    /*
5378                     * If there is a click listener, just call through to
5379                     * super, which will invoke it.
5380                     *
5381                     * If there isn't a click listener, try to show the soft
5382                     * input method.  (It will also
5383                     * call performClick(), but that won't do anything in
5384                     * this case.)
5385                     */
5386                    if (mOnClickListener == null) {
5387                        if (mMovement != null && mText instanceof Editable
5388                                && mLayout != null && onCheckIsTextEditor()) {
5389                            InputMethodManager imm = InputMethodManager.peekInstance();
5390                            if (imm != null) {
5391                                imm.viewClicked(this);
5392                                imm.showSoftInput(this, 0);
5393                            }
5394                        }
5395                    }
5396                }
5397                return super.onKeyUp(keyCode, event);
5398
5399            case KeyEvent.KEYCODE_ENTER:
5400                mEnterKeyIsDown = false;
5401                if (event.hasNoModifiers()) {
5402                    if (mInputContentType != null
5403                            && mInputContentType.onEditorActionListener != null
5404                            && mInputContentType.enterDown) {
5405                        mInputContentType.enterDown = false;
5406                        if (mInputContentType.onEditorActionListener.onEditorAction(
5407                                this, EditorInfo.IME_NULL, event)) {
5408                            return true;
5409                        }
5410                    }
5411
5412                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5413                            || shouldAdvanceFocusOnEnter()) {
5414                        /*
5415                         * If there is a click listener, just call through to
5416                         * super, which will invoke it.
5417                         *
5418                         * If there isn't a click listener, try to advance focus,
5419                         * but still call through to super, which will reset the
5420                         * pressed state and longpress state.  (It will also
5421                         * call performClick(), but that won't do anything in
5422                         * this case.)
5423                         */
5424                        if (mOnClickListener == null) {
5425                            View v = focusSearch(FOCUS_DOWN);
5426
5427                            if (v != null) {
5428                                if (!v.requestFocus(FOCUS_DOWN)) {
5429                                    throw new IllegalStateException(
5430                                            "focus search returned a view " +
5431                                            "that wasn't able to take focus!");
5432                                }
5433
5434                                /*
5435                                 * Return true because we handled the key; super
5436                                 * will return false because there was no click
5437                                 * listener.
5438                                 */
5439                                super.onKeyUp(keyCode, event);
5440                                return true;
5441                            } else if ((event.getFlags()
5442                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5443                                // No target for next focus, but make sure the IME
5444                                // if this came from it.
5445                                InputMethodManager imm = InputMethodManager.peekInstance();
5446                                if (imm != null && imm.isActive(this)) {
5447                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5448                                }
5449                            }
5450                        }
5451                    }
5452                    return super.onKeyUp(keyCode, event);
5453                }
5454                break;
5455        }
5456
5457        if (mInput != null)
5458            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
5459                return true;
5460
5461        if (mMovement != null && mLayout != null)
5462            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5463                return true;
5464
5465        return super.onKeyUp(keyCode, event);
5466    }
5467
5468    @Override public boolean onCheckIsTextEditor() {
5469        return mInputType != EditorInfo.TYPE_NULL;
5470    }
5471
5472    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5473        if (onCheckIsTextEditor() && isEnabled()) {
5474            if (mInputMethodState == null) {
5475                mInputMethodState = new InputMethodState();
5476            }
5477            outAttrs.inputType = mInputType;
5478            if (mInputContentType != null) {
5479                outAttrs.imeOptions = mInputContentType.imeOptions;
5480                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
5481                outAttrs.actionLabel = mInputContentType.imeActionLabel;
5482                outAttrs.actionId = mInputContentType.imeActionId;
5483                outAttrs.extras = mInputContentType.extras;
5484            } else {
5485                outAttrs.imeOptions = EditorInfo.IME_NULL;
5486            }
5487            if (focusSearch(FOCUS_DOWN) != null) {
5488                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5489            }
5490            if (focusSearch(FOCUS_UP) != null) {
5491                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5492            }
5493            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5494                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
5495                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
5496                    // An action has not been set, but the enter key will move to
5497                    // the next focus, so set the action to that.
5498                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
5499                } else {
5500                    // An action has not been set, and there is no focus to move
5501                    // to, so let's just supply a "done" action.
5502                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
5503                }
5504                if (!shouldAdvanceFocusOnEnter()) {
5505                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5506                }
5507            }
5508            if (isMultilineInputType(outAttrs.inputType)) {
5509                // Multi-line text editors should always show an enter key.
5510                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5511            }
5512            outAttrs.hintText = mHint;
5513            if (mText instanceof Editable) {
5514                InputConnection ic = new EditableInputConnection(this);
5515                outAttrs.initialSelStart = getSelectionStart();
5516                outAttrs.initialSelEnd = getSelectionEnd();
5517                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
5518                return ic;
5519            }
5520        }
5521        return null;
5522    }
5523
5524    /**
5525     * If this TextView contains editable content, extract a portion of it
5526     * based on the information in <var>request</var> in to <var>outText</var>.
5527     * @return Returns true if the text was successfully extracted, else false.
5528     */
5529    public boolean extractText(ExtractedTextRequest request,
5530            ExtractedText outText) {
5531        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
5532                EXTRACT_UNKNOWN, outText);
5533    }
5534
5535    static final int EXTRACT_NOTHING = -2;
5536    static final int EXTRACT_UNKNOWN = -1;
5537
5538    boolean extractTextInternal(ExtractedTextRequest request,
5539            int partialStartOffset, int partialEndOffset, int delta,
5540            ExtractedText outText) {
5541        final CharSequence content = mText;
5542        if (content != null) {
5543            if (partialStartOffset != EXTRACT_NOTHING) {
5544                final int N = content.length();
5545                if (partialStartOffset < 0) {
5546                    outText.partialStartOffset = outText.partialEndOffset = -1;
5547                    partialStartOffset = 0;
5548                    partialEndOffset = N;
5549                } else {
5550                    // Now use the delta to determine the actual amount of text
5551                    // we need.
5552                    partialEndOffset += delta;
5553                    // Adjust offsets to ensure we contain full spans.
5554                    if (content instanceof Spanned) {
5555                        Spanned spanned = (Spanned)content;
5556                        Object[] spans = spanned.getSpans(partialStartOffset,
5557                                partialEndOffset, ParcelableSpan.class);
5558                        int i = spans.length;
5559                        while (i > 0) {
5560                            i--;
5561                            int j = spanned.getSpanStart(spans[i]);
5562                            if (j < partialStartOffset) partialStartOffset = j;
5563                            j = spanned.getSpanEnd(spans[i]);
5564                            if (j > partialEndOffset) partialEndOffset = j;
5565                        }
5566                    }
5567                    outText.partialStartOffset = partialStartOffset;
5568                    outText.partialEndOffset = partialEndOffset - delta;
5569
5570                    if (partialStartOffset > N) {
5571                        partialStartOffset = N;
5572                    } else if (partialStartOffset < 0) {
5573                        partialStartOffset = 0;
5574                    }
5575                    if (partialEndOffset > N) {
5576                        partialEndOffset = N;
5577                    } else if (partialEndOffset < 0) {
5578                        partialEndOffset = 0;
5579                    }
5580                }
5581                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
5582                    outText.text = content.subSequence(partialStartOffset,
5583                            partialEndOffset);
5584                } else {
5585                    outText.text = TextUtils.substring(content, partialStartOffset,
5586                            partialEndOffset);
5587                }
5588            } else {
5589                outText.partialStartOffset = 0;
5590                outText.partialEndOffset = 0;
5591                outText.text = "";
5592            }
5593            outText.flags = 0;
5594            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
5595                outText.flags |= ExtractedText.FLAG_SELECTING;
5596            }
5597            if (mSingleLine) {
5598                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
5599            }
5600            outText.startOffset = 0;
5601            outText.selectionStart = getSelectionStart();
5602            outText.selectionEnd = getSelectionEnd();
5603            return true;
5604        }
5605        return false;
5606    }
5607
5608    boolean reportExtractedText() {
5609        final InputMethodState ims = mInputMethodState;
5610        if (ims != null) {
5611            final boolean contentChanged = ims.mContentChanged;
5612            if (contentChanged || ims.mSelectionModeChanged) {
5613                ims.mContentChanged = false;
5614                ims.mSelectionModeChanged = false;
5615                final ExtractedTextRequest req = mInputMethodState.mExtracting;
5616                if (req != null) {
5617                    InputMethodManager imm = InputMethodManager.peekInstance();
5618                    if (imm != null) {
5619                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
5620                                + ims.mChangedStart + " end=" + ims.mChangedEnd
5621                                + " delta=" + ims.mChangedDelta);
5622                        if (ims.mChangedStart < 0 && !contentChanged) {
5623                            ims.mChangedStart = EXTRACT_NOTHING;
5624                        }
5625                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
5626                                ims.mChangedDelta, ims.mTmpExtracted)) {
5627                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
5628                                    + ims.mTmpExtracted.partialStartOffset
5629                                    + " end=" + ims.mTmpExtracted.partialEndOffset
5630                                    + ": " + ims.mTmpExtracted.text);
5631                            imm.updateExtractedText(this, req.token,
5632                                    mInputMethodState.mTmpExtracted);
5633                            ims.mChangedStart = EXTRACT_UNKNOWN;
5634                            ims.mChangedEnd = EXTRACT_UNKNOWN;
5635                            ims.mChangedDelta = 0;
5636                            ims.mContentChanged = false;
5637                            return true;
5638                        }
5639                    }
5640                }
5641            }
5642        }
5643        return false;
5644    }
5645
5646    /**
5647     * This is used to remove all style-impacting spans from text before new
5648     * extracted text is being replaced into it, so that we don't have any
5649     * lingering spans applied during the replace.
5650     */
5651    static void removeParcelableSpans(Spannable spannable, int start, int end) {
5652        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5653        int i = spans.length;
5654        while (i > 0) {
5655            i--;
5656            spannable.removeSpan(spans[i]);
5657        }
5658    }
5659
5660    /**
5661     * Apply to this text view the given extracted text, as previously
5662     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5663     */
5664    public void setExtractedText(ExtractedText text) {
5665        Editable content = getEditableText();
5666        if (text.text != null) {
5667            if (content == null) {
5668                setText(text.text, TextView.BufferType.EDITABLE);
5669            } else if (text.partialStartOffset < 0) {
5670                removeParcelableSpans(content, 0, content.length());
5671                content.replace(0, content.length(), text.text);
5672            } else {
5673                final int N = content.length();
5674                int start = text.partialStartOffset;
5675                if (start > N) start = N;
5676                int end = text.partialEndOffset;
5677                if (end > N) end = N;
5678                removeParcelableSpans(content, start, end);
5679                content.replace(start, end, text.text);
5680            }
5681        }
5682
5683        // Now set the selection position...  make sure it is in range, to
5684        // avoid crashes.  If this is a partial update, it is possible that
5685        // the underlying text may have changed, causing us problems here.
5686        // Also we just don't want to trust clients to do the right thing.
5687        Spannable sp = (Spannable)getText();
5688        final int N = sp.length();
5689        int start = text.selectionStart;
5690        if (start < 0) start = 0;
5691        else if (start > N) start = N;
5692        int end = text.selectionEnd;
5693        if (end < 0) end = 0;
5694        else if (end > N) end = N;
5695        Selection.setSelection(sp, start, end);
5696
5697        // Finally, update the selection mode.
5698        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5699            MetaKeyKeyListener.startSelecting(this, sp);
5700        } else {
5701            MetaKeyKeyListener.stopSelecting(this, sp);
5702        }
5703    }
5704
5705    /**
5706     * @hide
5707     */
5708    public void setExtracting(ExtractedTextRequest req) {
5709        if (mInputMethodState != null) {
5710            mInputMethodState.mExtracting = req;
5711        }
5712        // This stops a possible text selection mode. Maybe not intended.
5713        hideControllers();
5714    }
5715
5716    /**
5717     * Called by the framework in response to a text completion from
5718     * the current input method, provided by it calling
5719     * {@link InputConnection#commitCompletion
5720     * InputConnection.commitCompletion()}.  The default implementation does
5721     * nothing; text views that are supporting auto-completion should override
5722     * this to do their desired behavior.
5723     *
5724     * @param text The auto complete text the user has selected.
5725     */
5726    public void onCommitCompletion(CompletionInfo text) {
5727        // intentionally empty
5728    }
5729
5730    /**
5731     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5732     * a dictionnary) from the current input method, provided by it calling
5733     * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5734     * implementation flashes the background of the corrected word to provide feedback to the user.
5735     *
5736     * @param info The auto correct info about the text that was corrected.
5737     */
5738    public void onCommitCorrection(CorrectionInfo info) {
5739        if (mCorrectionHighlighter == null) {
5740            mCorrectionHighlighter = new CorrectionHighlighter();
5741        } else {
5742            mCorrectionHighlighter.invalidate(false);
5743        }
5744
5745        mCorrectionHighlighter.highlight(info);
5746    }
5747
5748    private class CorrectionHighlighter {
5749        private final Path mPath = new Path();
5750        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
5751        private int mStart, mEnd;
5752        private long mFadingStartTime;
5753        private final static int FADE_OUT_DURATION = 400;
5754
5755        public CorrectionHighlighter() {
5756            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
5757            mPaint.setStyle(Paint.Style.FILL);
5758        }
5759
5760        public void highlight(CorrectionInfo info) {
5761            mStart = info.getOffset();
5762            mEnd = mStart + info.getNewText().length();
5763            mFadingStartTime = SystemClock.uptimeMillis();
5764
5765            if (mStart < 0 || mEnd < 0) {
5766                stopAnimation();
5767            }
5768        }
5769
5770        public void draw(Canvas canvas, int cursorOffsetVertical) {
5771            if (updatePath() && updatePaint()) {
5772                if (cursorOffsetVertical != 0) {
5773                    canvas.translate(0, cursorOffsetVertical);
5774                }
5775
5776                canvas.drawPath(mPath, mPaint);
5777
5778                if (cursorOffsetVertical != 0) {
5779                    canvas.translate(0, -cursorOffsetVertical);
5780                }
5781                invalidate(true);
5782            } else {
5783                stopAnimation();
5784                invalidate(false);
5785            }
5786        }
5787
5788        private boolean updatePaint() {
5789            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
5790            if (duration > FADE_OUT_DURATION) return false;
5791
5792            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
5793            final int highlightColorAlpha = Color.alpha(mHighlightColor);
5794            final int color = (mHighlightColor & 0x00FFFFFF) +
5795                    ((int) (highlightColorAlpha * coef) << 24);
5796            mPaint.setColor(color);
5797            return true;
5798        }
5799
5800        private boolean updatePath() {
5801            final Layout layout = TextView.this.mLayout;
5802            if (layout == null) return false;
5803
5804            // Update in case text is edited while the animation is run
5805            final int length = mText.length();
5806            int start = Math.min(length, mStart);
5807            int end = Math.min(length, mEnd);
5808
5809            mPath.reset();
5810            TextView.this.mLayout.getSelectionPath(start, end, mPath);
5811            return true;
5812        }
5813
5814        private void invalidate(boolean delayed) {
5815            if (TextView.this.mLayout == null) return;
5816
5817            synchronized (sTempRect) {
5818                mPath.computeBounds(sTempRect, false);
5819
5820                int left = getCompoundPaddingLeft();
5821                int top = getExtendedPaddingTop() + getVerticalOffset(true);
5822
5823                if (delayed) {
5824                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
5825                            left + (int) sTempRect.left, top + (int) sTempRect.top,
5826                            left + (int) sTempRect.right, top + (int) sTempRect.bottom);
5827                } else {
5828                    TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top,
5829                            (int) sTempRect.right, (int) sTempRect.bottom);
5830                }
5831            }
5832        }
5833
5834        private void stopAnimation() {
5835            TextView.this.mCorrectionHighlighter = null;
5836        }
5837    }
5838
5839    public void beginBatchEdit() {
5840        mInBatchEditControllers = true;
5841        final InputMethodState ims = mInputMethodState;
5842        if (ims != null) {
5843            int nesting = ++ims.mBatchEditNesting;
5844            if (nesting == 1) {
5845                ims.mCursorChanged = false;
5846                ims.mChangedDelta = 0;
5847                if (ims.mContentChanged) {
5848                    // We already have a pending change from somewhere else,
5849                    // so turn this into a full update.
5850                    ims.mChangedStart = 0;
5851                    ims.mChangedEnd = mText.length();
5852                } else {
5853                    ims.mChangedStart = EXTRACT_UNKNOWN;
5854                    ims.mChangedEnd = EXTRACT_UNKNOWN;
5855                    ims.mContentChanged = false;
5856                }
5857                onBeginBatchEdit();
5858            }
5859        }
5860    }
5861
5862    public void endBatchEdit() {
5863        mInBatchEditControllers = false;
5864        final InputMethodState ims = mInputMethodState;
5865        if (ims != null) {
5866            int nesting = --ims.mBatchEditNesting;
5867            if (nesting == 0) {
5868                finishBatchEdit(ims);
5869            }
5870        }
5871    }
5872
5873    void ensureEndedBatchEdit() {
5874        final InputMethodState ims = mInputMethodState;
5875        if (ims != null && ims.mBatchEditNesting != 0) {
5876            ims.mBatchEditNesting = 0;
5877            finishBatchEdit(ims);
5878        }
5879    }
5880
5881    void finishBatchEdit(final InputMethodState ims) {
5882        onEndBatchEdit();
5883
5884        if (ims.mContentChanged || ims.mSelectionModeChanged) {
5885            updateAfterEdit();
5886            reportExtractedText();
5887        } else if (ims.mCursorChanged) {
5888            // Cheezy way to get us to report the current cursor location.
5889            invalidateCursor();
5890        }
5891    }
5892
5893    void updateAfterEdit() {
5894        invalidate();
5895        int curs = getSelectionStart();
5896
5897        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5898            registerForPreDraw();
5899        }
5900
5901        if (curs >= 0) {
5902            mHighlightPathBogus = true;
5903            makeBlink();
5904            bringPointIntoView(curs);
5905        }
5906
5907        checkForResize();
5908    }
5909
5910    /**
5911     * Called by the framework in response to a request to begin a batch
5912     * of edit operations through a call to link {@link #beginBatchEdit()}.
5913     */
5914    public void onBeginBatchEdit() {
5915        // intentionally empty
5916    }
5917
5918    /**
5919     * Called by the framework in response to a request to end a batch
5920     * of edit operations through a call to link {@link #endBatchEdit}.
5921     */
5922    public void onEndBatchEdit() {
5923        // intentionally empty
5924    }
5925
5926    /**
5927     * Called by the framework in response to a private command from the
5928     * current method, provided by it calling
5929     * {@link InputConnection#performPrivateCommand
5930     * InputConnection.performPrivateCommand()}.
5931     *
5932     * @param action The action name of the command.
5933     * @param data Any additional data for the command.  This may be null.
5934     * @return Return true if you handled the command, else false.
5935     */
5936    public boolean onPrivateIMECommand(String action, Bundle data) {
5937        return false;
5938    }
5939
5940    private void nullLayouts() {
5941        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5942            mSavedLayout = (BoringLayout) mLayout;
5943        }
5944        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5945            mSavedHintLayout = (BoringLayout) mHintLayout;
5946        }
5947
5948        mLayout = mHintLayout = null;
5949
5950        // Since it depends on the value of mLayout
5951        prepareCursorControllers();
5952    }
5953
5954    /**
5955     * Make a new Layout based on the already-measured size of the view,
5956     * on the assumption that it was measured correctly at some point.
5957     */
5958    private void assumeLayout() {
5959        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5960
5961        if (width < 1) {
5962            width = 0;
5963        }
5964
5965        int physicalWidth = width;
5966
5967        if (mHorizontallyScrolling) {
5968            width = VERY_WIDE;
5969        }
5970
5971        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5972                      physicalWidth, false);
5973    }
5974
5975    @Override
5976    protected void resetResolvedLayoutDirection() {
5977        super.resetResolvedLayoutDirection();
5978
5979        if (mLayoutAlignment != null &&
5980                (mTextAlign == TextAlign.VIEW_START ||
5981                mTextAlign == TextAlign.VIEW_END)) {
5982            mLayoutAlignment = null;
5983        }
5984    }
5985
5986    private Layout.Alignment getLayoutAlignment() {
5987        if (mLayoutAlignment == null) {
5988            Layout.Alignment alignment;
5989            TextAlign textAlign = mTextAlign;
5990            switch (textAlign) {
5991                case INHERIT:
5992                    // fall through to gravity temporarily
5993                    // intention is to inherit value through view hierarchy.
5994                case GRAVITY:
5995                    switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5996                        case Gravity.START:
5997                            alignment = Layout.Alignment.ALIGN_NORMAL;
5998                            break;
5999                        case Gravity.END:
6000                            alignment = Layout.Alignment.ALIGN_OPPOSITE;
6001                            break;
6002                        case Gravity.LEFT:
6003                            alignment = Layout.Alignment.ALIGN_LEFT;
6004                            break;
6005                        case Gravity.RIGHT:
6006                            alignment = Layout.Alignment.ALIGN_RIGHT;
6007                            break;
6008                        case Gravity.CENTER_HORIZONTAL:
6009                            alignment = Layout.Alignment.ALIGN_CENTER;
6010                            break;
6011                        default:
6012                            alignment = Layout.Alignment.ALIGN_NORMAL;
6013                            break;
6014                    }
6015                    break;
6016                case TEXT_START:
6017                    alignment = Layout.Alignment.ALIGN_NORMAL;
6018                    break;
6019                case TEXT_END:
6020                    alignment = Layout.Alignment.ALIGN_OPPOSITE;
6021                    break;
6022                case CENTER:
6023                    alignment = Layout.Alignment.ALIGN_CENTER;
6024                    break;
6025                case VIEW_START:
6026                    alignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6027                            Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6028                    break;
6029                case VIEW_END:
6030                    alignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6031                            Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6032                    break;
6033                default:
6034                    alignment = Layout.Alignment.ALIGN_NORMAL;
6035                    break;
6036            }
6037            mLayoutAlignment = alignment;
6038        }
6039        return mLayoutAlignment;
6040    }
6041
6042    /**
6043     * The width passed in is now the desired layout width,
6044     * not the full view width with padding.
6045     * {@hide}
6046     */
6047    protected void makeNewLayout(int w, int hintWidth,
6048                                 BoringLayout.Metrics boring,
6049                                 BoringLayout.Metrics hintBoring,
6050                                 int ellipsisWidth, boolean bringIntoView) {
6051        stopMarquee();
6052
6053        // Update "old" cached values
6054        mOldMaximum = mMaximum;
6055        mOldMaxMode = mMaxMode;
6056
6057        mHighlightPathBogus = true;
6058
6059        if (w < 0) {
6060            w = 0;
6061        }
6062        if (hintWidth < 0) {
6063            hintWidth = 0;
6064        }
6065
6066        Layout.Alignment alignment = getLayoutAlignment();
6067        boolean shouldEllipsize = mEllipsize != null && mInput == null;
6068
6069        if (mTextDir == null) {
6070            resolveTextDirection();
6071        }
6072        if (mText instanceof Spannable) {
6073            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
6074                    alignment, mTextDir, mSpacingMult,
6075                    mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
6076                    ellipsisWidth);
6077        } else {
6078            if (boring == UNKNOWN_BORING) {
6079                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6080                if (boring != null) {
6081                    mBoring = boring;
6082                }
6083            }
6084
6085            if (boring != null) {
6086                if (boring.width <= w &&
6087                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
6088                    if (mSavedLayout != null) {
6089                        mLayout = mSavedLayout.
6090                                replaceOrMake(mTransformed, mTextPaint,
6091                                w, alignment, mSpacingMult, mSpacingAdd,
6092                                boring, mIncludePad);
6093                    } else {
6094                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
6095                                w, alignment, mSpacingMult, mSpacingAdd,
6096                                boring, mIncludePad);
6097                    }
6098
6099                    mSavedLayout = (BoringLayout) mLayout;
6100                } else if (shouldEllipsize && boring.width <= w) {
6101                    if (mSavedLayout != null) {
6102                        mLayout = mSavedLayout.
6103                                replaceOrMake(mTransformed, mTextPaint,
6104                                w, alignment, mSpacingMult, mSpacingAdd,
6105                                boring, mIncludePad, mEllipsize,
6106                                ellipsisWidth);
6107                    } else {
6108                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
6109                                w, alignment, mSpacingMult, mSpacingAdd,
6110                                boring, mIncludePad, mEllipsize,
6111                                ellipsisWidth);
6112                    }
6113                } else if (shouldEllipsize) {
6114                    mLayout = new StaticLayout(mTransformed,
6115                                0, mTransformed.length(),
6116                                mTextPaint, w, alignment, mTextDir, mSpacingMult,
6117                                mSpacingAdd, mIncludePad, mEllipsize,
6118                                ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6119                } else {
6120                    mLayout = new StaticLayout(mTransformed, mTextPaint,
6121                            w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6122                            mIncludePad);
6123                }
6124            } else if (shouldEllipsize) {
6125                mLayout = new StaticLayout(mTransformed,
6126                            0, mTransformed.length(),
6127                            mTextPaint, w, alignment, mTextDir, mSpacingMult,
6128                            mSpacingAdd, mIncludePad, mEllipsize,
6129                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6130            } else {
6131                mLayout = new StaticLayout(mTransformed, mTextPaint,
6132                        w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6133                        mIncludePad);
6134            }
6135        }
6136
6137        shouldEllipsize = mEllipsize != null;
6138        mHintLayout = null;
6139
6140        if (mHint != null) {
6141            if (shouldEllipsize) hintWidth = w;
6142
6143            if (hintBoring == UNKNOWN_BORING) {
6144                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6145                                                   mHintBoring);
6146                if (hintBoring != null) {
6147                    mHintBoring = hintBoring;
6148                }
6149            }
6150
6151            if (hintBoring != null) {
6152                if (hintBoring.width <= hintWidth &&
6153                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6154                    if (mSavedHintLayout != null) {
6155                        mHintLayout = mSavedHintLayout.
6156                                replaceOrMake(mHint, mTextPaint,
6157                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6158                                hintBoring, mIncludePad);
6159                    } else {
6160                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6161                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6162                                hintBoring, mIncludePad);
6163                    }
6164
6165                    mSavedHintLayout = (BoringLayout) mHintLayout;
6166                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6167                    if (mSavedHintLayout != null) {
6168                        mHintLayout = mSavedHintLayout.
6169                                replaceOrMake(mHint, mTextPaint,
6170                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6171                                hintBoring, mIncludePad, mEllipsize,
6172                                ellipsisWidth);
6173                    } else {
6174                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
6175                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
6176                                hintBoring, mIncludePad, mEllipsize,
6177                                ellipsisWidth);
6178                    }
6179                } else if (shouldEllipsize) {
6180                    mHintLayout = new StaticLayout(mHint,
6181                                0, mHint.length(),
6182                                mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6183                                mSpacingAdd, mIncludePad, mEllipsize,
6184                                ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6185                } else {
6186                    mHintLayout = new StaticLayout(mHint, mTextPaint,
6187                            hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6188                            mIncludePad);
6189                }
6190            } else if (shouldEllipsize) {
6191                mHintLayout = new StaticLayout(mHint,
6192                            0, mHint.length(),
6193                            mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6194                            mSpacingAdd, mIncludePad, mEllipsize,
6195                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6196            } else {
6197                mHintLayout = new StaticLayout(mHint, mTextPaint,
6198                        hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6199                        mIncludePad);
6200            }
6201        }
6202
6203        if (bringIntoView) {
6204            registerForPreDraw();
6205        }
6206
6207        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6208            if (!compressText(ellipsisWidth)) {
6209                final int height = mLayoutParams.height;
6210                // If the size of the view does not depend on the size of the text, try to
6211                // start the marquee immediately
6212                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6213                    startMarquee();
6214                } else {
6215                    // Defer the start of the marquee until we know our width (see setFrame())
6216                    mRestartMarquee = true;
6217                }
6218            }
6219        }
6220
6221        // CursorControllers need a non-null mLayout
6222        prepareCursorControllers();
6223    }
6224
6225    private boolean compressText(float width) {
6226        if (isHardwareAccelerated()) return false;
6227
6228        // Only compress the text if it hasn't been compressed by the previous pass
6229        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6230                mTextPaint.getTextScaleX() == 1.0f) {
6231            final float textWidth = mLayout.getLineWidth(0);
6232            final float overflow = (textWidth + 1.0f - width) / width;
6233            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6234                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6235                post(new Runnable() {
6236                    public void run() {
6237                        requestLayout();
6238                    }
6239                });
6240                return true;
6241            }
6242        }
6243
6244        return false;
6245    }
6246
6247    private static int desired(Layout layout) {
6248        int n = layout.getLineCount();
6249        CharSequence text = layout.getText();
6250        float max = 0;
6251
6252        // if any line was wrapped, we can't use it.
6253        // but it's ok for the last line not to have a newline
6254
6255        for (int i = 0; i < n - 1; i++) {
6256            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6257                return -1;
6258        }
6259
6260        for (int i = 0; i < n; i++) {
6261            max = Math.max(max, layout.getLineWidth(i));
6262        }
6263
6264        return (int) FloatMath.ceil(max);
6265    }
6266
6267    /**
6268     * Set whether the TextView includes extra top and bottom padding to make
6269     * room for accents that go above the normal ascent and descent.
6270     * The default is true.
6271     *
6272     * @attr ref android.R.styleable#TextView_includeFontPadding
6273     */
6274    public void setIncludeFontPadding(boolean includepad) {
6275        if (mIncludePad != includepad) {
6276            mIncludePad = includepad;
6277
6278            if (mLayout != null) {
6279                nullLayouts();
6280                requestLayout();
6281                invalidate();
6282            }
6283        }
6284    }
6285
6286    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6287
6288    @Override
6289    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6290        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6291        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6292        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6293        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6294
6295        int width;
6296        int height;
6297
6298        BoringLayout.Metrics boring = UNKNOWN_BORING;
6299        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6300
6301        if (mTextDir == null) {
6302            resolveTextDirection();
6303        }
6304
6305        int des = -1;
6306        boolean fromexisting = false;
6307
6308        if (widthMode == MeasureSpec.EXACTLY) {
6309            // Parent has told us how big to be. So be it.
6310            width = widthSize;
6311        } else {
6312            if (mLayout != null && mEllipsize == null) {
6313                des = desired(mLayout);
6314            }
6315
6316            if (des < 0) {
6317                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6318                if (boring != null) {
6319                    mBoring = boring;
6320                }
6321            } else {
6322                fromexisting = true;
6323            }
6324
6325            if (boring == null || boring == UNKNOWN_BORING) {
6326                if (des < 0) {
6327                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6328                }
6329
6330                width = des;
6331            } else {
6332                width = boring.width;
6333            }
6334
6335            final Drawables dr = mDrawables;
6336            if (dr != null) {
6337                width = Math.max(width, dr.mDrawableWidthTop);
6338                width = Math.max(width, dr.mDrawableWidthBottom);
6339            }
6340
6341            if (mHint != null) {
6342                int hintDes = -1;
6343                int hintWidth;
6344
6345                if (mHintLayout != null && mEllipsize == null) {
6346                    hintDes = desired(mHintLayout);
6347                }
6348
6349                if (hintDes < 0) {
6350                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
6351                    if (hintBoring != null) {
6352                        mHintBoring = hintBoring;
6353                    }
6354                }
6355
6356                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6357                    if (hintDes < 0) {
6358                        hintDes = (int) FloatMath.ceil(
6359                                Layout.getDesiredWidth(mHint, mTextPaint));
6360                    }
6361
6362                    hintWidth = hintDes;
6363                } else {
6364                    hintWidth = hintBoring.width;
6365                }
6366
6367                if (hintWidth > width) {
6368                    width = hintWidth;
6369                }
6370            }
6371
6372            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6373
6374            if (mMaxWidthMode == EMS) {
6375                width = Math.min(width, mMaxWidth * getLineHeight());
6376            } else {
6377                width = Math.min(width, mMaxWidth);
6378            }
6379
6380            if (mMinWidthMode == EMS) {
6381                width = Math.max(width, mMinWidth * getLineHeight());
6382            } else {
6383                width = Math.max(width, mMinWidth);
6384            }
6385
6386            // Check against our minimum width
6387            width = Math.max(width, getSuggestedMinimumWidth());
6388
6389            if (widthMode == MeasureSpec.AT_MOST) {
6390                width = Math.min(widthSize, width);
6391            }
6392        }
6393
6394        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6395        int unpaddedWidth = want;
6396
6397        if (mHorizontallyScrolling) want = VERY_WIDE;
6398
6399        int hintWant = want;
6400        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6401
6402        if (mLayout == null) {
6403            makeNewLayout(want, hintWant, boring, hintBoring,
6404                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6405        } else {
6406            final boolean layoutChanged = (mLayout.getWidth() != want) ||
6407                    (hintWidth != hintWant) ||
6408                    (mLayout.getEllipsizedWidth() !=
6409                            width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6410
6411            final boolean widthChanged = (mHint == null) &&
6412                    (mEllipsize == null) &&
6413                    (want > mLayout.getWidth()) &&
6414                    (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6415
6416            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6417
6418            if (layoutChanged || maximumChanged) {
6419                if (!maximumChanged && widthChanged) {
6420                    mLayout.increaseWidthTo(want);
6421                } else {
6422                    makeNewLayout(want, hintWant, boring, hintBoring,
6423                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6424                }
6425            } else {
6426                // Nothing has changed
6427            }
6428        }
6429
6430        if (heightMode == MeasureSpec.EXACTLY) {
6431            // Parent has told us how big to be. So be it.
6432            height = heightSize;
6433            mDesiredHeightAtMeasure = -1;
6434        } else {
6435            int desired = getDesiredHeight();
6436
6437            height = desired;
6438            mDesiredHeightAtMeasure = desired;
6439
6440            if (heightMode == MeasureSpec.AT_MOST) {
6441                height = Math.min(desired, heightSize);
6442            }
6443        }
6444
6445        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6446        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6447            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6448        }
6449
6450        /*
6451         * We didn't let makeNewLayout() register to bring the cursor into view,
6452         * so do it here if there is any possibility that it is needed.
6453         */
6454        if (mMovement != null ||
6455            mLayout.getWidth() > unpaddedWidth ||
6456            mLayout.getHeight() > unpaddedHeight) {
6457            registerForPreDraw();
6458        } else {
6459            scrollTo(0, 0);
6460        }
6461
6462        setMeasuredDimension(width, height);
6463    }
6464
6465    private int getDesiredHeight() {
6466        return Math.max(
6467                getDesiredHeight(mLayout, true),
6468                getDesiredHeight(mHintLayout, mEllipsize != null));
6469    }
6470
6471    private int getDesiredHeight(Layout layout, boolean cap) {
6472        if (layout == null) {
6473            return 0;
6474        }
6475
6476        int linecount = layout.getLineCount();
6477        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6478        int desired = layout.getLineTop(linecount);
6479
6480        final Drawables dr = mDrawables;
6481        if (dr != null) {
6482            desired = Math.max(desired, dr.mDrawableHeightLeft);
6483            desired = Math.max(desired, dr.mDrawableHeightRight);
6484        }
6485
6486        desired += pad;
6487
6488        if (mMaxMode == LINES) {
6489            /*
6490             * Don't cap the hint to a certain number of lines.
6491             * (Do cap it, though, if we have a maximum pixel height.)
6492             */
6493            if (cap) {
6494                if (linecount > mMaximum) {
6495                    desired = layout.getLineTop(mMaximum);
6496
6497                    if (dr != null) {
6498                        desired = Math.max(desired, dr.mDrawableHeightLeft);
6499                        desired = Math.max(desired, dr.mDrawableHeightRight);
6500                    }
6501
6502                    desired += pad;
6503                    linecount = mMaximum;
6504                }
6505            }
6506        } else {
6507            desired = Math.min(desired, mMaximum);
6508        }
6509
6510        if (mMinMode == LINES) {
6511            if (linecount < mMinimum) {
6512                desired += getLineHeight() * (mMinimum - linecount);
6513            }
6514        } else {
6515            desired = Math.max(desired, mMinimum);
6516        }
6517
6518        // Check against our minimum height
6519        desired = Math.max(desired, getSuggestedMinimumHeight());
6520
6521        return desired;
6522    }
6523
6524    /**
6525     * Check whether a change to the existing text layout requires a
6526     * new view layout.
6527     */
6528    private void checkForResize() {
6529        boolean sizeChanged = false;
6530
6531        if (mLayout != null) {
6532            // Check if our width changed
6533            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6534                sizeChanged = true;
6535                invalidate();
6536            }
6537
6538            // Check if our height changed
6539            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6540                int desiredHeight = getDesiredHeight();
6541
6542                if (desiredHeight != this.getHeight()) {
6543                    sizeChanged = true;
6544                }
6545            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6546                if (mDesiredHeightAtMeasure >= 0) {
6547                    int desiredHeight = getDesiredHeight();
6548
6549                    if (desiredHeight != mDesiredHeightAtMeasure) {
6550                        sizeChanged = true;
6551                    }
6552                }
6553            }
6554        }
6555
6556        if (sizeChanged) {
6557            requestLayout();
6558            // caller will have already invalidated
6559        }
6560    }
6561
6562    /**
6563     * Check whether entirely new text requires a new view layout
6564     * or merely a new text layout.
6565     */
6566    private void checkForRelayout() {
6567        // If we have a fixed width, we can just swap in a new text layout
6568        // if the text height stays the same or if the view height is fixed.
6569
6570        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6571                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6572                (mHint == null || mHintLayout != null) &&
6573                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6574            // Static width, so try making a new text layout.
6575
6576            int oldht = mLayout.getHeight();
6577            int want = mLayout.getWidth();
6578            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6579
6580            /*
6581             * No need to bring the text into view, since the size is not
6582             * changing (unless we do the requestLayout(), in which case it
6583             * will happen at measure).
6584             */
6585            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6586                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6587                          false);
6588
6589            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6590                // In a fixed-height view, so use our new text layout.
6591                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6592                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6593                    invalidate();
6594                    return;
6595                }
6596
6597                // Dynamic height, but height has stayed the same,
6598                // so use our new text layout.
6599                if (mLayout.getHeight() == oldht &&
6600                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6601                    invalidate();
6602                    return;
6603                }
6604            }
6605
6606            // We lose: the height has changed and we have a dynamic height.
6607            // Request a new view layout using our new text layout.
6608            requestLayout();
6609            invalidate();
6610        } else {
6611            // Dynamic width, so we have no choice but to request a new
6612            // view layout with a new text layout.
6613            nullLayouts();
6614            requestLayout();
6615            invalidate();
6616        }
6617    }
6618
6619    /**
6620     * Returns true if anything changed.
6621     */
6622    private boolean bringTextIntoView() {
6623        int line = 0;
6624        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6625            line = mLayout.getLineCount() - 1;
6626        }
6627
6628        Layout.Alignment a = mLayout.getParagraphAlignment(line);
6629        int dir = mLayout.getParagraphDirection(line);
6630        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6631        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6632        int ht = mLayout.getHeight();
6633
6634        int scrollx, scrolly;
6635
6636        // Convert to left, center, or right alignment.
6637        if (a == Layout.Alignment.ALIGN_NORMAL) {
6638            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6639                Layout.Alignment.ALIGN_RIGHT;
6640        } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6641            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6642                Layout.Alignment.ALIGN_LEFT;
6643        }
6644
6645        if (a == Layout.Alignment.ALIGN_CENTER) {
6646            /*
6647             * Keep centered if possible, or, if it is too wide to fit,
6648             * keep leading edge in view.
6649             */
6650
6651            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6652            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6653
6654            if (right - left < hspace) {
6655                scrollx = (right + left) / 2 - hspace / 2;
6656            } else {
6657                if (dir < 0) {
6658                    scrollx = right - hspace;
6659                } else {
6660                    scrollx = left;
6661                }
6662            }
6663        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
6664            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6665            scrollx = right - hspace;
6666        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
6667            scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6668        }
6669
6670        if (ht < vspace) {
6671            scrolly = 0;
6672        } else {
6673            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6674                scrolly = ht - vspace;
6675            } else {
6676                scrolly = 0;
6677            }
6678        }
6679
6680        if (scrollx != mScrollX || scrolly != mScrollY) {
6681            scrollTo(scrollx, scrolly);
6682            return true;
6683        } else {
6684            return false;
6685        }
6686    }
6687
6688    /**
6689     * Move the point, specified by the offset, into the view if it is needed.
6690     * This has to be called after layout. Returns true if anything changed.
6691     */
6692    public boolean bringPointIntoView(int offset) {
6693        boolean changed = false;
6694
6695        if (mLayout == null) return changed;
6696
6697        int line = mLayout.getLineForOffset(offset);
6698
6699        // FIXME: Is it okay to truncate this, or should we round?
6700        final int x = (int)mLayout.getPrimaryHorizontal(offset);
6701        final int top = mLayout.getLineTop(line);
6702        final int bottom = mLayout.getLineTop(line + 1);
6703
6704        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6705        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6706        int ht = mLayout.getHeight();
6707
6708        int grav;
6709
6710        switch (mLayout.getParagraphAlignment(line)) {
6711            case ALIGN_LEFT:
6712                grav = 1;
6713                break;
6714            case ALIGN_RIGHT:
6715                grav = -1;
6716                break;
6717            case ALIGN_NORMAL:
6718                grav = mLayout.getParagraphDirection(line);
6719                break;
6720            case ALIGN_OPPOSITE:
6721                grav = -mLayout.getParagraphDirection(line);
6722                break;
6723            case ALIGN_CENTER:
6724            default:
6725                grav = 0;
6726                break;
6727        }
6728
6729        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6730        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6731
6732        int hslack = (bottom - top) / 2;
6733        int vslack = hslack;
6734
6735        if (vslack > vspace / 4)
6736            vslack = vspace / 4;
6737        if (hslack > hspace / 4)
6738            hslack = hspace / 4;
6739
6740        int hs = mScrollX;
6741        int vs = mScrollY;
6742
6743        if (top - vs < vslack)
6744            vs = top - vslack;
6745        if (bottom - vs > vspace - vslack)
6746            vs = bottom - (vspace - vslack);
6747        if (ht - vs < vspace)
6748            vs = ht - vspace;
6749        if (0 - vs > 0)
6750            vs = 0;
6751
6752        if (grav != 0) {
6753            if (x - hs < hslack) {
6754                hs = x - hslack;
6755            }
6756            if (x - hs > hspace - hslack) {
6757                hs = x - (hspace - hslack);
6758            }
6759        }
6760
6761        if (grav < 0) {
6762            if (left - hs > 0)
6763                hs = left;
6764            if (right - hs < hspace)
6765                hs = right - hspace;
6766        } else if (grav > 0) {
6767            if (right - hs < hspace)
6768                hs = right - hspace;
6769            if (left - hs > 0)
6770                hs = left;
6771        } else /* grav == 0 */ {
6772            if (right - left <= hspace) {
6773                /*
6774                 * If the entire text fits, center it exactly.
6775                 */
6776                hs = left - (hspace - (right - left)) / 2;
6777            } else if (x > right - hslack) {
6778                /*
6779                 * If we are near the right edge, keep the right edge
6780                 * at the edge of the view.
6781                 */
6782                hs = right - hspace;
6783            } else if (x < left + hslack) {
6784                /*
6785                 * If we are near the left edge, keep the left edge
6786                 * at the edge of the view.
6787                 */
6788                hs = left;
6789            } else if (left > hs) {
6790                /*
6791                 * Is there whitespace visible at the left?  Fix it if so.
6792                 */
6793                hs = left;
6794            } else if (right < hs + hspace) {
6795                /*
6796                 * Is there whitespace visible at the right?  Fix it if so.
6797                 */
6798                hs = right - hspace;
6799            } else {
6800                /*
6801                 * Otherwise, float as needed.
6802                 */
6803                if (x - hs < hslack) {
6804                    hs = x - hslack;
6805                }
6806                if (x - hs > hspace - hslack) {
6807                    hs = x - (hspace - hslack);
6808                }
6809            }
6810        }
6811
6812        if (hs != mScrollX || vs != mScrollY) {
6813            if (mScroller == null) {
6814                scrollTo(hs, vs);
6815            } else {
6816                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6817                int dx = hs - mScrollX;
6818                int dy = vs - mScrollY;
6819
6820                if (duration > ANIMATED_SCROLL_GAP) {
6821                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
6822                    awakenScrollBars(mScroller.getDuration());
6823                    invalidate();
6824                } else {
6825                    if (!mScroller.isFinished()) {
6826                        mScroller.abortAnimation();
6827                    }
6828
6829                    scrollBy(dx, dy);
6830                }
6831
6832                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6833            }
6834
6835            changed = true;
6836        }
6837
6838        if (isFocused()) {
6839            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6840            // requestRectangleOnScreen() is in terms of content coordinates.
6841
6842            if (mTempRect == null) mTempRect = new Rect();
6843            // The offsets here are to ensure the rectangle we are using is
6844            // within our view bounds, in case the cursor is on the far left
6845            // or right.  If it isn't withing the bounds, then this request
6846            // will be ignored.
6847            mTempRect.set(x - 2, top, x + 2, bottom);
6848            getInterestingRect(mTempRect, line);
6849            mTempRect.offset(mScrollX, mScrollY);
6850
6851            if (requestRectangleOnScreen(mTempRect)) {
6852                changed = true;
6853            }
6854        }
6855
6856        return changed;
6857    }
6858
6859    /**
6860     * Move the cursor, if needed, so that it is at an offset that is visible
6861     * to the user.  This will not move the cursor if it represents more than
6862     * one character (a selection range).  This will only work if the
6863     * TextView contains spannable text; otherwise it will do nothing.
6864     *
6865     * @return True if the cursor was actually moved, false otherwise.
6866     */
6867    public boolean moveCursorToVisibleOffset() {
6868        if (!(mText instanceof Spannable)) {
6869            return false;
6870        }
6871        int start = getSelectionStart();
6872        int end = getSelectionEnd();
6873        if (start != end) {
6874            return false;
6875        }
6876
6877        // First: make sure the line is visible on screen:
6878
6879        int line = mLayout.getLineForOffset(start);
6880
6881        final int top = mLayout.getLineTop(line);
6882        final int bottom = mLayout.getLineTop(line + 1);
6883        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6884        int vslack = (bottom - top) / 2;
6885        if (vslack > vspace / 4)
6886            vslack = vspace / 4;
6887        final int vs = mScrollY;
6888
6889        if (top < (vs+vslack)) {
6890            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6891        } else if (bottom > (vspace+vs-vslack)) {
6892            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6893        }
6894
6895        // Next: make sure the character is visible on screen:
6896
6897        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6898        final int hs = mScrollX;
6899        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6900        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
6901
6902        // line might contain bidirectional text
6903        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6904        final int highChar = leftChar > rightChar ? leftChar : rightChar;
6905
6906        int newStart = start;
6907        if (newStart < lowChar) {
6908            newStart = lowChar;
6909        } else if (newStart > highChar) {
6910            newStart = highChar;
6911        }
6912
6913        if (newStart != start) {
6914            Selection.setSelection((Spannable)mText, newStart);
6915            return true;
6916        }
6917
6918        return false;
6919    }
6920
6921    @Override
6922    public void computeScroll() {
6923        if (mScroller != null) {
6924            if (mScroller.computeScrollOffset()) {
6925                mScrollX = mScroller.getCurrX();
6926                mScrollY = mScroller.getCurrY();
6927                invalidateParentCaches();
6928                postInvalidate();  // So we draw again
6929            }
6930        }
6931    }
6932
6933    private void getInterestingRect(Rect r, int line) {
6934        convertFromViewportToContentCoordinates(r);
6935
6936        // Rectangle can can be expanded on first and last line to take
6937        // padding into account.
6938        // TODO Take left/right padding into account too?
6939        if (line == 0) r.top -= getExtendedPaddingTop();
6940        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6941    }
6942
6943    private void convertFromViewportToContentCoordinates(Rect r) {
6944        final int horizontalOffset = viewportToContentHorizontalOffset();
6945        r.left += horizontalOffset;
6946        r.right += horizontalOffset;
6947
6948        final int verticalOffset = viewportToContentVerticalOffset();
6949        r.top += verticalOffset;
6950        r.bottom += verticalOffset;
6951    }
6952
6953    private int viewportToContentHorizontalOffset() {
6954        return getCompoundPaddingLeft() - mScrollX;
6955    }
6956
6957    private int viewportToContentVerticalOffset() {
6958        int offset = getExtendedPaddingTop() - mScrollY;
6959        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6960            offset += getVerticalOffset(false);
6961        }
6962        return offset;
6963    }
6964
6965    @Override
6966    public void debug(int depth) {
6967        super.debug(depth);
6968
6969        String output = debugIndent(depth);
6970        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6971                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6972                + "} ";
6973
6974        if (mText != null) {
6975
6976            output += "mText=\"" + mText + "\" ";
6977            if (mLayout != null) {
6978                output += "mLayout width=" + mLayout.getWidth()
6979                        + " height=" + mLayout.getHeight();
6980            }
6981        } else {
6982            output += "mText=NULL";
6983        }
6984        Log.d(VIEW_LOG_TAG, output);
6985    }
6986
6987    /**
6988     * Convenience for {@link Selection#getSelectionStart}.
6989     */
6990    @ViewDebug.ExportedProperty(category = "text")
6991    public int getSelectionStart() {
6992        return Selection.getSelectionStart(getText());
6993    }
6994
6995    /**
6996     * Convenience for {@link Selection#getSelectionEnd}.
6997     */
6998    @ViewDebug.ExportedProperty(category = "text")
6999    public int getSelectionEnd() {
7000        return Selection.getSelectionEnd(getText());
7001    }
7002
7003    /**
7004     * Return true iff there is a selection inside this text view.
7005     */
7006    public boolean hasSelection() {
7007        final int selectionStart = getSelectionStart();
7008        final int selectionEnd = getSelectionEnd();
7009
7010        return selectionStart >= 0 && selectionStart != selectionEnd;
7011    }
7012
7013    /**
7014     * Sets the properties of this field (lines, horizontally scrolling,
7015     * transformation method) to be for a single-line input.
7016     *
7017     * @attr ref android.R.styleable#TextView_singleLine
7018     */
7019    public void setSingleLine() {
7020        setSingleLine(true);
7021    }
7022
7023    /**
7024     * Sets the properties of this field to transform input to ALL CAPS
7025     * display. This may use a "small caps" formatting if available.
7026     * This setting will be ignored if this field is editable or selectable.
7027     *
7028     * This call replaces the current transformation method. Disabling this
7029     * will not necessarily restore the previous behavior from before this
7030     * was enabled.
7031     *
7032     * @see #setTransformationMethod(TransformationMethod)
7033     * @attr ref android.R.styleable#TextView_textAllCaps
7034     */
7035    public void setAllCaps(boolean allCaps) {
7036        if (allCaps) {
7037            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7038        } else {
7039            setTransformationMethod(null);
7040        }
7041    }
7042
7043    /**
7044     * If true, sets the properties of this field (number of lines, horizontally scrolling,
7045     * transformation method) to be for a single-line input; if false, restores these to the default
7046     * conditions.
7047     *
7048     * Note that the default conditions are not necessarily those that were in effect prior this
7049     * method, and you may want to reset these properties to your custom values.
7050     *
7051     * @attr ref android.R.styleable#TextView_singleLine
7052     */
7053    @android.view.RemotableViewMethod
7054    public void setSingleLine(boolean singleLine) {
7055        // Could be used, but may break backward compatibility.
7056        // if (mSingleLine == singleLine) return;
7057        setInputTypeSingleLine(singleLine);
7058        applySingleLine(singleLine, true, true);
7059    }
7060
7061    /**
7062     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7063     * @param singleLine
7064     */
7065    private void setInputTypeSingleLine(boolean singleLine) {
7066        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7067            if (singleLine) {
7068                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7069            } else {
7070                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7071            }
7072        }
7073    }
7074
7075    private void applySingleLine(boolean singleLine, boolean applyTransformation,
7076            boolean changeMaxLines) {
7077        mSingleLine = singleLine;
7078        if (singleLine) {
7079            setLines(1);
7080            setHorizontallyScrolling(true);
7081            if (applyTransformation) {
7082                setTransformationMethod(SingleLineTransformationMethod.getInstance());
7083            }
7084        } else {
7085            if (changeMaxLines) {
7086                setMaxLines(Integer.MAX_VALUE);
7087            }
7088            setHorizontallyScrolling(false);
7089            if (applyTransformation) {
7090                setTransformationMethod(null);
7091            }
7092        }
7093    }
7094
7095    /**
7096     * Causes words in the text that are longer than the view is wide
7097     * to be ellipsized instead of broken in the middle.  You may also
7098     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7099     * to constrain the text to a single line.  Use <code>null</code>
7100     * to turn off ellipsizing.
7101     *
7102     * If {@link #setMaxLines} has been used to set two or more lines,
7103     * {@link android.text.TextUtils.TruncateAt#END} and
7104     * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7105     * (other ellipsizing types will not do anything).
7106     *
7107     * @attr ref android.R.styleable#TextView_ellipsize
7108     */
7109    public void setEllipsize(TextUtils.TruncateAt where) {
7110        // TruncateAt is an enum. != comparison is ok between these singleton objects.
7111        if (mEllipsize != where) {
7112            mEllipsize = where;
7113
7114            if (mLayout != null) {
7115                nullLayouts();
7116                requestLayout();
7117                invalidate();
7118            }
7119        }
7120    }
7121
7122    /**
7123     * Sets how many times to repeat the marquee animation. Only applied if the
7124     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7125     *
7126     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7127     */
7128    public void setMarqueeRepeatLimit(int marqueeLimit) {
7129        mMarqueeRepeatLimit = marqueeLimit;
7130    }
7131
7132    /**
7133     * Returns where, if anywhere, words that are longer than the view
7134     * is wide should be ellipsized.
7135     */
7136    @ViewDebug.ExportedProperty
7137    public TextUtils.TruncateAt getEllipsize() {
7138        return mEllipsize;
7139    }
7140
7141    /**
7142     * Set the TextView so that when it takes focus, all the text is
7143     * selected.
7144     *
7145     * @attr ref android.R.styleable#TextView_selectAllOnFocus
7146     */
7147    @android.view.RemotableViewMethod
7148    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7149        mSelectAllOnFocus = selectAllOnFocus;
7150
7151        if (selectAllOnFocus && !(mText instanceof Spannable)) {
7152            setText(mText, BufferType.SPANNABLE);
7153        }
7154    }
7155
7156    /**
7157     * Set whether the cursor is visible.  The default is true.
7158     *
7159     * @attr ref android.R.styleable#TextView_cursorVisible
7160     */
7161    @android.view.RemotableViewMethod
7162    public void setCursorVisible(boolean visible) {
7163        if (mCursorVisible != visible) {
7164            mCursorVisible = visible;
7165            invalidate();
7166
7167            makeBlink();
7168
7169            // InsertionPointCursorController depends on mCursorVisible
7170            prepareCursorControllers();
7171        }
7172    }
7173
7174    private boolean isCursorVisible() {
7175        return mCursorVisible && isTextEditable();
7176    }
7177
7178    private boolean canMarquee() {
7179        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7180        return width > 0 && mLayout.getLineWidth(0) > width;
7181    }
7182
7183    private void startMarquee() {
7184        // Do not ellipsize EditText
7185        if (mInput != null) return;
7186
7187        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7188            return;
7189        }
7190
7191        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7192                getLineCount() == 1 && canMarquee()) {
7193
7194            if (mMarquee == null) mMarquee = new Marquee(this);
7195            mMarquee.start(mMarqueeRepeatLimit);
7196        }
7197    }
7198
7199    private void stopMarquee() {
7200        if (mMarquee != null && !mMarquee.isStopped()) {
7201            mMarquee.stop();
7202        }
7203    }
7204
7205    private void startStopMarquee(boolean start) {
7206        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7207            if (start) {
7208                startMarquee();
7209            } else {
7210                stopMarquee();
7211            }
7212        }
7213    }
7214
7215    private static final class Marquee extends Handler {
7216        // TODO: Add an option to configure this
7217        private static final float MARQUEE_DELTA_MAX = 0.07f;
7218        private static final int MARQUEE_DELAY = 1200;
7219        private static final int MARQUEE_RESTART_DELAY = 1200;
7220        private static final int MARQUEE_RESOLUTION = 1000 / 30;
7221        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
7222
7223        private static final byte MARQUEE_STOPPED = 0x0;
7224        private static final byte MARQUEE_STARTING = 0x1;
7225        private static final byte MARQUEE_RUNNING = 0x2;
7226
7227        private static final int MESSAGE_START = 0x1;
7228        private static final int MESSAGE_TICK = 0x2;
7229        private static final int MESSAGE_RESTART = 0x3;
7230
7231        private final WeakReference<TextView> mView;
7232
7233        private byte mStatus = MARQUEE_STOPPED;
7234        private final float mScrollUnit;
7235        private float mMaxScroll;
7236        float mMaxFadeScroll;
7237        private float mGhostStart;
7238        private float mGhostOffset;
7239        private float mFadeStop;
7240        private int mRepeatLimit;
7241
7242        float mScroll;
7243
7244        Marquee(TextView v) {
7245            final float density = v.getContext().getResources().getDisplayMetrics().density;
7246            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
7247            mView = new WeakReference<TextView>(v);
7248        }
7249
7250        @Override
7251        public void handleMessage(Message msg) {
7252            switch (msg.what) {
7253                case MESSAGE_START:
7254                    mStatus = MARQUEE_RUNNING;
7255                    tick();
7256                    break;
7257                case MESSAGE_TICK:
7258                    tick();
7259                    break;
7260                case MESSAGE_RESTART:
7261                    if (mStatus == MARQUEE_RUNNING) {
7262                        if (mRepeatLimit >= 0) {
7263                            mRepeatLimit--;
7264                        }
7265                        start(mRepeatLimit);
7266                    }
7267                    break;
7268            }
7269        }
7270
7271        void tick() {
7272            if (mStatus != MARQUEE_RUNNING) {
7273                return;
7274            }
7275
7276            removeMessages(MESSAGE_TICK);
7277
7278            final TextView textView = mView.get();
7279            if (textView != null && (textView.isFocused() || textView.isSelected())) {
7280                mScroll += mScrollUnit;
7281                if (mScroll > mMaxScroll) {
7282                    mScroll = mMaxScroll;
7283                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
7284                } else {
7285                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
7286                }
7287                textView.invalidate();
7288            }
7289        }
7290
7291        void stop() {
7292            mStatus = MARQUEE_STOPPED;
7293            removeMessages(MESSAGE_START);
7294            removeMessages(MESSAGE_RESTART);
7295            removeMessages(MESSAGE_TICK);
7296            resetScroll();
7297        }
7298
7299        private void resetScroll() {
7300            mScroll = 0.0f;
7301            final TextView textView = mView.get();
7302            if (textView != null) textView.invalidate();
7303        }
7304
7305        void start(int repeatLimit) {
7306            if (repeatLimit == 0) {
7307                stop();
7308                return;
7309            }
7310            mRepeatLimit = repeatLimit;
7311            final TextView textView = mView.get();
7312            if (textView != null && textView.mLayout != null) {
7313                mStatus = MARQUEE_STARTING;
7314                mScroll = 0.0f;
7315                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
7316                        textView.getCompoundPaddingRight();
7317                final float lineWidth = textView.mLayout.getLineWidth(0);
7318                final float gap = textWidth / 3.0f;
7319                mGhostStart = lineWidth - textWidth + gap;
7320                mMaxScroll = mGhostStart + textWidth;
7321                mGhostOffset = lineWidth + gap;
7322                mFadeStop = lineWidth + textWidth / 6.0f;
7323                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
7324
7325                textView.invalidate();
7326                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
7327            }
7328        }
7329
7330        float getGhostOffset() {
7331            return mGhostOffset;
7332        }
7333
7334        boolean shouldDrawLeftFade() {
7335            return mScroll <= mFadeStop;
7336        }
7337
7338        boolean shouldDrawGhost() {
7339            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
7340        }
7341
7342        boolean isRunning() {
7343            return mStatus == MARQUEE_RUNNING;
7344        }
7345
7346        boolean isStopped() {
7347            return mStatus == MARQUEE_STOPPED;
7348        }
7349    }
7350
7351    /**
7352     * This method is called when the text is changed, in case any subclasses
7353     * would like to know.
7354     *
7355     * Within <code>text</code>, the <code>lengthAfter</code> characters
7356     * beginning at <code>start</code> have just replaced old text that had
7357     * length <code>lengthBefore</code>. It is an error to attempt to make
7358     * changes to <code>text</code> from this callback.
7359     *
7360     * @param text The text the TextView is displaying
7361     * @param start The offset of the start of the range of the text that was
7362     * modified
7363     * @param lengthBefore The length of the former text that has been replaced
7364     * @param lengthAfter The length of the replacement modified text
7365     */
7366    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7367        // intentionally empty, template pattern method can be overridden by subclasses
7368    }
7369
7370    /**
7371     * This method is called when the selection has changed, in case any
7372     * subclasses would like to know.
7373     *
7374     * @param selStart The new selection start location.
7375     * @param selEnd The new selection end location.
7376     */
7377    protected void onSelectionChanged(int selStart, int selEnd) {
7378        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7379        if (mSpellChecker != null) {
7380            mSpellChecker.onSelectionChanged();
7381        }
7382    }
7383
7384    /**
7385     * Adds a TextWatcher to the list of those whose methods are called
7386     * whenever this TextView's text changes.
7387     * <p>
7388     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7389     * not called after {@link #setText} calls.  Now, doing {@link #setText}
7390     * if there are any text changed listeners forces the buffer type to
7391     * Editable if it would not otherwise be and does call this method.
7392     */
7393    public void addTextChangedListener(TextWatcher watcher) {
7394        if (mListeners == null) {
7395            mListeners = new ArrayList<TextWatcher>();
7396        }
7397
7398        mListeners.add(watcher);
7399    }
7400
7401    /**
7402     * Removes the specified TextWatcher from the list of those whose
7403     * methods are called
7404     * whenever this TextView's text changes.
7405     */
7406    public void removeTextChangedListener(TextWatcher watcher) {
7407        if (mListeners != null) {
7408            int i = mListeners.indexOf(watcher);
7409
7410            if (i >= 0) {
7411                mListeners.remove(i);
7412            }
7413        }
7414    }
7415
7416    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7417        if (mListeners != null) {
7418            final ArrayList<TextWatcher> list = mListeners;
7419            final int count = list.size();
7420            for (int i = 0; i < count; i++) {
7421                list.get(i).beforeTextChanged(text, start, before, after);
7422            }
7423        }
7424
7425        // The spans that are inside or intersect the modified region no longer make sense
7426        removeIntersectingSpans(start, start + before, SpellCheckSpan.class);
7427        removeIntersectingSpans(start, start + before, SuggestionSpan.class);
7428    }
7429
7430    // Removes all spans that are inside or actually overlap the start..end range
7431    private <T> void removeIntersectingSpans(int start, int end, Class<T> type) {
7432        if (!(mText instanceof Editable)) return;
7433        Editable text = (Editable) mText;
7434
7435        T[] spans = text.getSpans(start, end, type);
7436        final int length = spans.length;
7437        for (int i = 0; i < length; i++) {
7438            final int s = text.getSpanStart(spans[i]);
7439            final int e = text.getSpanEnd(spans[i]);
7440            if (e == start || s == end) break;
7441            text.removeSpan(spans[i]);
7442        }
7443    }
7444
7445    /**
7446     * Not private so it can be called from an inner class without going
7447     * through a thunk.
7448     */
7449    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7450        if (mListeners != null) {
7451            final ArrayList<TextWatcher> list = mListeners;
7452            final int count = list.size();
7453            for (int i = 0; i < count; i++) {
7454                list.get(i).onTextChanged(text, start, before, after);
7455            }
7456        }
7457    }
7458
7459    /**
7460     * Not private so it can be called from an inner class without going
7461     * through a thunk.
7462     */
7463    void sendAfterTextChanged(Editable text) {
7464        if (mListeners != null) {
7465            final ArrayList<TextWatcher> list = mListeners;
7466            final int count = list.size();
7467            for (int i = 0; i < count; i++) {
7468                list.get(i).afterTextChanged(text);
7469            }
7470        }
7471    }
7472
7473    /**
7474     * Not private so it can be called from an inner class without going
7475     * through a thunk.
7476     */
7477    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
7478        final InputMethodState ims = mInputMethodState;
7479        if (ims == null || ims.mBatchEditNesting == 0) {
7480            updateAfterEdit();
7481        }
7482        if (ims != null) {
7483            ims.mContentChanged = true;
7484            if (ims.mChangedStart < 0) {
7485                ims.mChangedStart = start;
7486                ims.mChangedEnd = start+before;
7487            } else {
7488                ims.mChangedStart = Math.min(ims.mChangedStart, start);
7489                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
7490            }
7491            ims.mChangedDelta += after-before;
7492        }
7493
7494        sendOnTextChanged(buffer, start, before, after);
7495        onTextChanged(buffer, start, before, after);
7496
7497        // The WordIterator text change listener may be called after this one.
7498        // Make sure this changed text is rescanned before the iterator is used on it.
7499        getWordIterator().forceUpdate();
7500        updateSpellCheckSpans(start, start + after);
7501
7502        // Hide the controllers if the amount of content changed
7503        if (before != after) {
7504            hideControllers();
7505        }
7506    }
7507
7508    /**
7509     * Not private so it can be called from an inner class without going
7510     * through a thunk.
7511     */
7512    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
7513        // XXX Make the start and end move together if this ends up
7514        // spending too much time invalidating.
7515
7516        boolean selChanged = false;
7517        int newSelStart=-1, newSelEnd=-1;
7518
7519        final InputMethodState ims = mInputMethodState;
7520
7521        if (what == Selection.SELECTION_END) {
7522            mHighlightPathBogus = true;
7523            selChanged = true;
7524            newSelEnd = newStart;
7525
7526            if (!isFocused()) {
7527                mSelectionMoved = true;
7528            }
7529
7530            if (oldStart >= 0 || newStart >= 0) {
7531                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
7532                registerForPreDraw();
7533                makeBlink();
7534            }
7535        }
7536
7537        if (what == Selection.SELECTION_START) {
7538            mHighlightPathBogus = true;
7539            selChanged = true;
7540            newSelStart = newStart;
7541
7542            if (!isFocused()) {
7543                mSelectionMoved = true;
7544            }
7545
7546            if (oldStart >= 0 || newStart >= 0) {
7547                int end = Selection.getSelectionEnd(buf);
7548                invalidateCursor(end, oldStart, newStart);
7549            }
7550        }
7551
7552        if (selChanged) {
7553            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7554                if (newSelStart < 0) {
7555                    newSelStart = Selection.getSelectionStart(buf);
7556                }
7557                if (newSelEnd < 0) {
7558                    newSelEnd = Selection.getSelectionEnd(buf);
7559                }
7560                onSelectionChanged(newSelStart, newSelEnd);
7561            }
7562        }
7563
7564        if (what instanceof UpdateAppearance ||
7565            what instanceof ParagraphStyle) {
7566            if (ims == null || ims.mBatchEditNesting == 0) {
7567                invalidate();
7568                mHighlightPathBogus = true;
7569                checkForResize();
7570            } else {
7571                ims.mContentChanged = true;
7572            }
7573        }
7574
7575        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7576            mHighlightPathBogus = true;
7577            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7578                ims.mSelectionModeChanged = true;
7579            }
7580
7581            if (Selection.getSelectionStart(buf) >= 0) {
7582                if (ims == null || ims.mBatchEditNesting == 0) {
7583                    invalidateCursor();
7584                } else {
7585                    ims.mCursorChanged = true;
7586                }
7587            }
7588        }
7589
7590        if (what instanceof ParcelableSpan) {
7591            // If this is a span that can be sent to a remote process,
7592            // the current extract editor would be interested in it.
7593            if (ims != null && ims.mExtracting != null) {
7594                if (ims.mBatchEditNesting != 0) {
7595                    if (oldStart >= 0) {
7596                        if (ims.mChangedStart > oldStart) {
7597                            ims.mChangedStart = oldStart;
7598                        }
7599                        if (ims.mChangedStart > oldEnd) {
7600                            ims.mChangedStart = oldEnd;
7601                        }
7602                    }
7603                    if (newStart >= 0) {
7604                        if (ims.mChangedStart > newStart) {
7605                            ims.mChangedStart = newStart;
7606                        }
7607                        if (ims.mChangedStart > newEnd) {
7608                            ims.mChangedStart = newEnd;
7609                        }
7610                    }
7611                } else {
7612                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7613                            + oldStart + "-" + oldEnd + ","
7614                            + newStart + "-" + newEnd + what);
7615                    ims.mContentChanged = true;
7616                }
7617            }
7618        }
7619
7620        if (what instanceof SpellCheckSpan) {
7621            if (newStart < 0) {
7622                getSpellChecker().removeSpellCheckSpan((SpellCheckSpan) what);
7623            } else if (oldStart < 0) {
7624                getSpellChecker().addSpellCheckSpan((SpellCheckSpan) what);
7625            }
7626        }
7627    }
7628
7629    /**
7630     * Create new SpellCheckSpans on the modified region.
7631     */
7632    private void updateSpellCheckSpans(int start, int end) {
7633        if (!(mText instanceof Editable) || !isSuggestionsEnabled()) return;
7634        Editable text = (Editable) mText;
7635
7636        WordIterator wordIterator = getWordIterator();
7637        wordIterator.setCharSequence(text);
7638
7639        // Move back to the beginning of the current word, if any
7640        int wordStart = wordIterator.preceding(start);
7641        int wordEnd;
7642        if (wordStart == BreakIterator.DONE) {
7643            wordEnd = wordIterator.following(start);
7644            if (wordEnd != BreakIterator.DONE) {
7645                wordStart = wordIterator.getBeginning(wordEnd);
7646            }
7647        } else {
7648            wordEnd = wordIterator.getEnd(wordStart);
7649        }
7650        if (wordEnd == BreakIterator.DONE) {
7651            return;
7652        }
7653
7654        // Iterate over the newly added text and schedule new SpellCheckSpans
7655        while (wordStart <= end) {
7656            if (wordEnd >= start) {
7657                // A word across the interval boundaries must remove boundary edition spans
7658                if (wordStart < start && wordEnd > start) {
7659                    removeEditionSpansAt(start, text);
7660                }
7661
7662                if (wordStart < end && wordEnd > end) {
7663                    removeEditionSpansAt(end, text);
7664                }
7665
7666                // Do not create new boundary spans if they already exist
7667                boolean createSpellCheckSpan = true;
7668                if (wordEnd == start) {
7669                    SpellCheckSpan[] spellCheckSpans = text.getSpans(start, start,
7670                            SpellCheckSpan.class);
7671                    if (spellCheckSpans.length > 0) createSpellCheckSpan = false;
7672                }
7673
7674                if (wordStart == end) {
7675                    SpellCheckSpan[] spellCheckSpans = text.getSpans(end, end,
7676                            SpellCheckSpan.class);
7677                    if (spellCheckSpans.length > 0) createSpellCheckSpan = false;
7678                }
7679
7680                if (createSpellCheckSpan) {
7681                    text.setSpan(new SpellCheckSpan(), wordStart, wordEnd,
7682                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
7683                }
7684            }
7685
7686            // iterate word by word
7687            wordEnd = wordIterator.following(wordEnd);
7688            if (wordEnd == BreakIterator.DONE) return;
7689            wordStart = wordIterator.getBeginning(wordEnd);
7690            if (wordStart == BreakIterator.DONE) {
7691                Log.e(LOG_TAG, "Unable to find word beginning from " + wordEnd + "in " + mText);
7692                return;
7693            }
7694        }
7695    }
7696
7697    private static void removeEditionSpansAt(int offset, Editable text) {
7698        SuggestionSpan[] suggestionSpans = text.getSpans(offset, offset, SuggestionSpan.class);
7699        for (int i = 0; i < suggestionSpans.length; i++) {
7700            text.removeSpan(suggestionSpans[i]);
7701        }
7702        SpellCheckSpan[] spellCheckSpans = text.getSpans(offset, offset, SpellCheckSpan.class);
7703        for (int i = 0; i < spellCheckSpans.length; i++) {
7704            text.removeSpan(spellCheckSpans[i]);
7705        }
7706    }
7707
7708    private class ChangeWatcher implements TextWatcher, SpanWatcher {
7709
7710        private CharSequence mBeforeText;
7711
7712        public void beforeTextChanged(CharSequence buffer, int start,
7713                                      int before, int after) {
7714            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
7715                    + " before=" + before + " after=" + after + ": " + buffer);
7716
7717            if (AccessibilityManager.getInstance(mContext).isEnabled()
7718                    && !isPasswordInputType(mInputType)
7719                    && !hasPasswordTransformationMethod()) {
7720                mBeforeText = buffer.toString();
7721            }
7722
7723            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
7724        }
7725
7726        public void onTextChanged(CharSequence buffer, int start,
7727                                  int before, int after) {
7728            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
7729                    + " before=" + before + " after=" + after + ": " + buffer);
7730            TextView.this.handleTextChanged(buffer, start, before, after);
7731
7732            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
7733                    (isFocused() || isSelected() && isShown())) {
7734                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
7735                mBeforeText = null;
7736            }
7737        }
7738
7739        public void afterTextChanged(Editable buffer) {
7740            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
7741            TextView.this.sendAfterTextChanged(buffer);
7742
7743            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
7744                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
7745            }
7746        }
7747
7748        public void onSpanChanged(Spannable buf,
7749                                  Object what, int s, int e, int st, int en) {
7750            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
7751                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
7752            TextView.this.spanChange(buf, what, s, st, e, en);
7753        }
7754
7755        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
7756            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
7757                    + " what=" + what + ": " + buf);
7758            TextView.this.spanChange(buf, what, -1, s, -1, e);
7759        }
7760
7761        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
7762            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
7763                    + " what=" + what + ": " + buf);
7764            TextView.this.spanChange(buf, what, s, -1, e, -1);
7765        }
7766    }
7767
7768    /**
7769     * @hide
7770     */
7771    @Override
7772    public void dispatchFinishTemporaryDetach() {
7773        mDispatchTemporaryDetach = true;
7774        super.dispatchFinishTemporaryDetach();
7775        mDispatchTemporaryDetach = false;
7776    }
7777
7778    @Override
7779    public void onStartTemporaryDetach() {
7780        super.onStartTemporaryDetach();
7781        // Only track when onStartTemporaryDetach() is called directly,
7782        // usually because this instance is an editable field in a list
7783        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
7784
7785        // Because of View recycling in ListView, there is no easy way to know when a TextView with
7786        // selection becomes visible again. Until a better solution is found, stop text selection
7787        // mode (if any) as soon as this TextView is recycled.
7788        hideControllers();
7789    }
7790
7791    @Override
7792    public void onFinishTemporaryDetach() {
7793        super.onFinishTemporaryDetach();
7794        // Only track when onStartTemporaryDetach() is called directly,
7795        // usually because this instance is an editable field in a list
7796        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
7797    }
7798
7799    @Override
7800    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7801        if (mTemporaryDetach) {
7802            // If we are temporarily in the detach state, then do nothing.
7803            super.onFocusChanged(focused, direction, previouslyFocusedRect);
7804            return;
7805        }
7806
7807        mShowCursor = SystemClock.uptimeMillis();
7808
7809        ensureEndedBatchEdit();
7810
7811        if (focused) {
7812            int selStart = getSelectionStart();
7813            int selEnd = getSelectionEnd();
7814
7815            // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
7816            // mode for these, unless there was a specific selection already started.
7817            final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
7818                    selEnd == mText.length();
7819            mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
7820
7821            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
7822                // If a tap was used to give focus to that view, move cursor at tap position.
7823                // Has to be done before onTakeFocus, which can be overloaded.
7824                final int lastTapPosition = getLastTapPosition();
7825                if (lastTapPosition >= 0) {
7826                    Selection.setSelection((Spannable) mText, lastTapPosition);
7827                }
7828
7829                if (mMovement != null) {
7830                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
7831                }
7832
7833                // The DecorView does not have focus when the 'Done' ExtractEditText button is
7834                // pressed. Since it is the ViewAncestor's mView, it requests focus before
7835                // ExtractEditText clears focus, which gives focus to the ExtractEditText.
7836                // This special case ensure that we keep current selection in that case.
7837                // It would be better to know why the DecorView does not have focus at that time.
7838                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
7839                        selStart >= 0 && selEnd >= 0) {
7840                    /*
7841                     * Someone intentionally set the selection, so let them
7842                     * do whatever it is that they wanted to do instead of
7843                     * the default on-focus behavior.  We reset the selection
7844                     * here instead of just skipping the onTakeFocus() call
7845                     * because some movement methods do something other than
7846                     * just setting the selection in theirs and we still
7847                     * need to go through that path.
7848                     */
7849                    Selection.setSelection((Spannable) mText, selStart, selEnd);
7850                }
7851
7852                if (mSelectAllOnFocus) {
7853                    selectAll();
7854                }
7855
7856                mTouchFocusSelected = true;
7857            }
7858
7859            mFrozenWithFocus = false;
7860            mSelectionMoved = false;
7861
7862            if (mText instanceof Spannable) {
7863                Spannable sp = (Spannable) mText;
7864                MetaKeyKeyListener.resetMetaState(sp);
7865            }
7866
7867            makeBlink();
7868
7869            if (mError != null) {
7870                showError();
7871            }
7872        } else {
7873            if (mError != null) {
7874                hideError();
7875            }
7876            // Don't leave us in the middle of a batch edit.
7877            onEndBatchEdit();
7878
7879            if (this instanceof ExtractEditText) {
7880                // terminateTextSelectionMode removes selection, which we want to keep when
7881                // ExtractEditText goes out of focus.
7882                final int selStart = getSelectionStart();
7883                final int selEnd = getSelectionEnd();
7884                hideControllers();
7885                Selection.setSelection((Spannable) mText, selStart, selEnd);
7886            } else {
7887                hideControllers();
7888            }
7889
7890            // No need to create the controller
7891            if (mSelectionModifierCursorController != null) {
7892                mSelectionModifierCursorController.resetTouchOffsets();
7893            }
7894        }
7895
7896        startStopMarquee(focused);
7897
7898        if (mTransformation != null) {
7899            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
7900        }
7901
7902        super.onFocusChanged(focused, direction, previouslyFocusedRect);
7903    }
7904
7905    private int getLastTapPosition() {
7906        // No need to create the controller at that point, no last tap position saved
7907        if (mSelectionModifierCursorController != null) {
7908            int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
7909            if (lastTapPosition >= 0) {
7910                // Safety check, should not be possible.
7911                if (lastTapPosition > mText.length()) {
7912                    Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
7913                            + mText.length() + ")");
7914                    lastTapPosition = mText.length();
7915                }
7916                return lastTapPosition;
7917            }
7918        }
7919
7920        return -1;
7921    }
7922
7923    @Override
7924    public void onWindowFocusChanged(boolean hasWindowFocus) {
7925        super.onWindowFocusChanged(hasWindowFocus);
7926
7927        if (hasWindowFocus) {
7928            if (mBlink != null) {
7929                mBlink.uncancel();
7930                makeBlink();
7931            }
7932        } else {
7933            if (mBlink != null) {
7934                mBlink.cancel();
7935            }
7936            // Don't leave us in the middle of a batch edit.
7937            onEndBatchEdit();
7938            if (mInputContentType != null) {
7939                mInputContentType.enterDown = false;
7940            }
7941
7942            hideControllers();
7943        }
7944
7945        startStopMarquee(hasWindowFocus);
7946    }
7947
7948    private void removeSpans(int start, int end, Class<?> type) {
7949        if (mText instanceof Editable) {
7950            Editable editable = ((Editable) mText);
7951            Object[] spans = editable.getSpans(start, end, type);
7952            final int length = spans.length;
7953            for (int i = 0; i < length; i++) {
7954                editable.removeSpan(spans[i]);
7955            }
7956        }
7957    }
7958
7959    @Override
7960    protected void onVisibilityChanged(View changedView, int visibility) {
7961        super.onVisibilityChanged(changedView, visibility);
7962        if (visibility != VISIBLE) {
7963            hideControllers();
7964        }
7965    }
7966
7967    /**
7968     * Use {@link BaseInputConnection#removeComposingSpans
7969     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7970     * state from this text view.
7971     */
7972    public void clearComposingText() {
7973        if (mText instanceof Spannable) {
7974            BaseInputConnection.removeComposingSpans((Spannable)mText);
7975        }
7976    }
7977
7978    @Override
7979    public void setSelected(boolean selected) {
7980        boolean wasSelected = isSelected();
7981
7982        super.setSelected(selected);
7983
7984        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7985            if (selected) {
7986                startMarquee();
7987            } else {
7988                stopMarquee();
7989            }
7990        }
7991    }
7992
7993    @Override
7994    public boolean onTouchEvent(MotionEvent event) {
7995        final int action = event.getActionMasked();
7996
7997        if (hasSelectionController()) {
7998            getSelectionController().onTouchEvent(event);
7999        }
8000
8001        if (action == MotionEvent.ACTION_DOWN) {
8002            mLastDownPositionX = event.getX();
8003            mLastDownPositionY = event.getY();
8004
8005            // Reset this state; it will be re-set if super.onTouchEvent
8006            // causes focus to move to the view.
8007            mTouchFocusSelected = false;
8008            mIgnoreActionUpEvent = false;
8009        }
8010
8011        final boolean superResult = super.onTouchEvent(event);
8012
8013        /*
8014         * Don't handle the release after a long press, because it will
8015         * move the selection away from whatever the menu action was
8016         * trying to affect.
8017         */
8018        if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8019            mDiscardNextActionUp = false;
8020            return superResult;
8021        }
8022
8023        final boolean touchIsFinished = action == MotionEvent.ACTION_UP && !mIgnoreActionUpEvent &&
8024                isFocused();
8025
8026        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8027                && mText instanceof Spannable && mLayout != null) {
8028            boolean handled = false;
8029
8030            if (mMovement != null) {
8031                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8032            }
8033
8034            if (mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable && touchIsFinished) {
8035                // The LinkMovementMethod which should handle taps on links has not been installed
8036                // to support text selection. We reproduce its behavior here to open links.
8037                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8038                        getSelectionEnd(), ClickableSpan.class);
8039
8040                if (links.length != 0) {
8041                    links[0].onClick(this);
8042                    handled = true;
8043                }
8044            }
8045
8046            if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
8047                // Show the IME, except when selecting in read-only text.
8048                final InputMethodManager imm = InputMethodManager.peekInstance();
8049                if (imm != null) {
8050                    imm.viewClicked(this);
8051                }
8052                if (!mTextIsSelectable) {
8053                    handled |= imm != null && imm.showSoftInput(this, 0);
8054                }
8055
8056                boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
8057                if (!selectAllGotFocus && hasSelection()) {
8058                    startSelectionActionMode();
8059                } else {
8060                    hideControllers();
8061                    if (!selectAllGotFocus && mText.length() > 0) {
8062                        if (isCursorInsideEasyCorrectionSpan()) {
8063                            showSuggestions();
8064                        } else if (hasInsertionController()) {
8065                            getInsertionController().show();
8066                        }
8067                    }
8068                }
8069
8070                handled = true;
8071            }
8072
8073            if (handled) {
8074                return true;
8075            }
8076        }
8077
8078        return superResult;
8079    }
8080
8081    /**
8082     * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}.
8083     */
8084    private boolean isCursorInsideSuggestionSpan() {
8085        if (!(mText instanceof Spannable)) return false;
8086
8087        SuggestionSpan[] suggestionSpans = ((Spannable) mText).getSpans(getSelectionStart(),
8088                getSelectionEnd(), SuggestionSpan.class);
8089        return (suggestionSpans.length > 0);
8090    }
8091
8092    /**
8093     * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with
8094     * {@link SuggestionSpan#FLAG_EASY_CORRECT} set.
8095     */
8096    private boolean isCursorInsideEasyCorrectionSpan() {
8097        Spannable spannable = (Spannable) mText;
8098        SuggestionSpan[] suggestionSpans = spannable.getSpans(getSelectionStart(),
8099                getSelectionEnd(), SuggestionSpan.class);
8100        for (int i = 0; i < suggestionSpans.length; i++) {
8101            if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) {
8102                return true;
8103            }
8104        }
8105        return false;
8106    }
8107
8108    @Override
8109    public boolean onGenericMotionEvent(MotionEvent event) {
8110        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8111            try {
8112                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8113                    return true;
8114                }
8115            } catch (AbstractMethodError ex) {
8116                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8117                // Ignore its absence in case third party applications implemented the
8118                // interface directly.
8119            }
8120        }
8121        return super.onGenericMotionEvent(event);
8122    }
8123
8124    private void prepareCursorControllers() {
8125        boolean windowSupportsHandles = false;
8126
8127        ViewGroup.LayoutParams params = getRootView().getLayoutParams();
8128        if (params instanceof WindowManager.LayoutParams) {
8129            WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
8130            windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
8131                    || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
8132        }
8133
8134        mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
8135        mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
8136                mLayout != null;
8137
8138        if (!mInsertionControllerEnabled) {
8139            hideInsertionPointCursorController();
8140            if (mInsertionPointCursorController != null) {
8141                mInsertionPointCursorController.onDetached();
8142                mInsertionPointCursorController = null;
8143            }
8144        }
8145
8146        if (!mSelectionControllerEnabled) {
8147            stopSelectionActionMode();
8148            if (mSelectionModifierCursorController != null) {
8149                mSelectionModifierCursorController.onDetached();
8150                mSelectionModifierCursorController = null;
8151            }
8152        }
8153    }
8154
8155    /**
8156     * @return True iff this TextView contains a text that can be edited, or if this is
8157     * a selectable TextView.
8158     */
8159    private boolean isTextEditable() {
8160        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8161    }
8162
8163    /**
8164     * Returns true, only while processing a touch gesture, if the initial
8165     * touch down event caused focus to move to the text view and as a result
8166     * its selection changed.  Only valid while processing the touch gesture
8167     * of interest.
8168     */
8169    public boolean didTouchFocusSelect() {
8170        return mTouchFocusSelected;
8171    }
8172
8173    @Override
8174    public void cancelLongPress() {
8175        super.cancelLongPress();
8176        mIgnoreActionUpEvent = true;
8177    }
8178
8179    @Override
8180    public boolean onTrackballEvent(MotionEvent event) {
8181        if (mMovement != null && mText instanceof Spannable &&
8182            mLayout != null) {
8183            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8184                return true;
8185            }
8186        }
8187
8188        return super.onTrackballEvent(event);
8189    }
8190
8191    public void setScroller(Scroller s) {
8192        mScroller = s;
8193    }
8194
8195    private static class Blink extends Handler implements Runnable {
8196        private final WeakReference<TextView> mView;
8197        private boolean mCancelled;
8198
8199        public Blink(TextView v) {
8200            mView = new WeakReference<TextView>(v);
8201        }
8202
8203        public void run() {
8204            if (mCancelled) {
8205                return;
8206            }
8207
8208            removeCallbacks(Blink.this);
8209
8210            TextView tv = mView.get();
8211
8212            if (tv != null && tv.shouldBlink()) {
8213                if (tv.mLayout != null) {
8214                    tv.invalidateCursorPath();
8215                }
8216
8217                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
8218            }
8219        }
8220
8221        void cancel() {
8222            if (!mCancelled) {
8223                removeCallbacks(Blink.this);
8224                mCancelled = true;
8225            }
8226        }
8227
8228        void uncancel() {
8229            mCancelled = false;
8230        }
8231    }
8232
8233    /**
8234     * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
8235     */
8236    private boolean shouldBlink() {
8237        if (!isFocused()) return false;
8238
8239        final int start = getSelectionStart();
8240        if (start < 0) return false;
8241
8242        final int end = getSelectionEnd();
8243        if (end < 0) return false;
8244
8245        return start == end;
8246    }
8247
8248    private void makeBlink() {
8249        if (isCursorVisible()) {
8250            if (shouldBlink()) {
8251                mShowCursor = SystemClock.uptimeMillis();
8252                if (mBlink == null) mBlink = new Blink(this);
8253                mBlink.removeCallbacks(mBlink);
8254                mBlink.postAtTime(mBlink, mShowCursor + BLINK);
8255            }
8256        } else {
8257            if (mBlink != null) mBlink.removeCallbacks(mBlink);
8258        }
8259    }
8260
8261    @Override
8262    protected float getLeftFadingEdgeStrength() {
8263        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
8264        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8265            if (mMarquee != null && !mMarquee.isStopped()) {
8266                final Marquee marquee = mMarquee;
8267                if (marquee.shouldDrawLeftFade()) {
8268                    return marquee.mScroll / getHorizontalFadingEdgeLength();
8269                } else {
8270                    return 0.0f;
8271                }
8272            } else if (getLineCount() == 1) {
8273                final int layoutDirection = getResolvedLayoutDirection();
8274                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8275                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8276                    case Gravity.LEFT:
8277                        return 0.0f;
8278                    case Gravity.RIGHT:
8279                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
8280                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
8281                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8282                    case Gravity.CENTER_HORIZONTAL:
8283                        return 0.0f;
8284                }
8285            }
8286        }
8287        return super.getLeftFadingEdgeStrength();
8288    }
8289
8290    @Override
8291    protected float getRightFadingEdgeStrength() {
8292        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
8293        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8294            if (mMarquee != null && !mMarquee.isStopped()) {
8295                final Marquee marquee = mMarquee;
8296                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
8297            } else if (getLineCount() == 1) {
8298                final int layoutDirection = getResolvedLayoutDirection();
8299                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8300                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8301                    case Gravity.LEFT:
8302                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8303                                getCompoundPaddingRight();
8304                        final float lineWidth = mLayout.getLineWidth(0);
8305                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8306                    case Gravity.RIGHT:
8307                        return 0.0f;
8308                    case Gravity.CENTER_HORIZONTAL:
8309                    case Gravity.FILL_HORIZONTAL:
8310                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8311                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8312                                getHorizontalFadingEdgeLength();
8313                }
8314            }
8315        }
8316        return super.getRightFadingEdgeStrength();
8317    }
8318
8319    @Override
8320    protected int computeHorizontalScrollRange() {
8321        if (mLayout != null) {
8322            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8323                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8324        }
8325
8326        return super.computeHorizontalScrollRange();
8327    }
8328
8329    @Override
8330    protected int computeVerticalScrollRange() {
8331        if (mLayout != null)
8332            return mLayout.getHeight();
8333
8334        return super.computeVerticalScrollRange();
8335    }
8336
8337    @Override
8338    protected int computeVerticalScrollExtent() {
8339        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8340    }
8341
8342    @Override
8343    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched) {
8344        if (TextUtils.isEmpty(searched)) {
8345            return;
8346        }
8347        CharSequence thisText = getText();
8348        if (TextUtils.isEmpty(thisText)) {
8349            return;
8350        }
8351        String searchedLowerCase = searched.toString().toLowerCase();
8352        String thisTextLowerCase = thisText.toString().toLowerCase();
8353        if (thisTextLowerCase.contains(searchedLowerCase)) {
8354            outViews.add(this);
8355        }
8356    }
8357
8358    public enum BufferType {
8359        NORMAL, SPANNABLE, EDITABLE,
8360    }
8361
8362    /**
8363     * Returns the TextView_textColor attribute from the
8364     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
8365     * from the TextView_textAppearance attribute, if TextView_textColor
8366     * was not set directly.
8367     */
8368    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8369        ColorStateList colors;
8370        colors = attrs.getColorStateList(com.android.internal.R.styleable.
8371                                         TextView_textColor);
8372
8373        if (colors == null) {
8374            int ap = attrs.getResourceId(com.android.internal.R.styleable.
8375                                         TextView_textAppearance, -1);
8376            if (ap != -1) {
8377                TypedArray appearance;
8378                appearance = context.obtainStyledAttributes(ap,
8379                                            com.android.internal.R.styleable.TextAppearance);
8380                colors = appearance.getColorStateList(com.android.internal.R.styleable.
8381                                                  TextAppearance_textColor);
8382                appearance.recycle();
8383            }
8384        }
8385
8386        return colors;
8387    }
8388
8389    /**
8390     * Returns the default color from the TextView_textColor attribute
8391     * from the AttributeSet, if set, or the default color from the
8392     * TextAppearance_textColor from the TextView_textAppearance attribute,
8393     * if TextView_textColor was not set directly.
8394     */
8395    public static int getTextColor(Context context,
8396                                   TypedArray attrs,
8397                                   int def) {
8398        ColorStateList colors = getTextColors(context, attrs);
8399
8400        if (colors == null) {
8401            return def;
8402        } else {
8403            return colors.getDefaultColor();
8404        }
8405    }
8406
8407    @Override
8408    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8409        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
8410        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
8411            switch (keyCode) {
8412            case KeyEvent.KEYCODE_A:
8413                if (canSelectText()) {
8414                    return onTextContextMenuItem(ID_SELECT_ALL);
8415                }
8416                break;
8417            case KeyEvent.KEYCODE_X:
8418                if (canCut()) {
8419                    return onTextContextMenuItem(ID_CUT);
8420                }
8421                break;
8422            case KeyEvent.KEYCODE_C:
8423                if (canCopy()) {
8424                    return onTextContextMenuItem(ID_COPY);
8425                }
8426                break;
8427            case KeyEvent.KEYCODE_V:
8428                if (canPaste()) {
8429                    return onTextContextMenuItem(ID_PASTE);
8430                }
8431                break;
8432            }
8433        }
8434        return super.onKeyShortcut(keyCode, event);
8435    }
8436
8437    /**
8438     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8439     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8440     * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient.
8441     */
8442    private boolean canSelectText() {
8443        return hasSelectionController() && mText.length() != 0;
8444    }
8445
8446    /**
8447     * Test based on the <i>intrinsic</i> charateristics of the TextView.
8448     * The text must be spannable and the movement method must allow for arbitary selection.
8449     *
8450     * See also {@link #canSelectText()}.
8451     */
8452    private boolean textCanBeSelected() {
8453        // prepareCursorController() relies on this method.
8454        // If you change this condition, make sure prepareCursorController is called anywhere
8455        // the value of this condition might be changed.
8456        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8457        return isTextEditable() || (mTextIsSelectable && mText instanceof Spannable && isEnabled());
8458    }
8459
8460    private boolean canCut() {
8461        if (hasPasswordTransformationMethod()) {
8462            return false;
8463        }
8464
8465        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
8466            return true;
8467        }
8468
8469        return false;
8470    }
8471
8472    private boolean canCopy() {
8473        if (hasPasswordTransformationMethod()) {
8474            return false;
8475        }
8476
8477        if (mText.length() > 0 && hasSelection()) {
8478            return true;
8479        }
8480
8481        return false;
8482    }
8483
8484    private boolean canPaste() {
8485        return (mText instanceof Editable &&
8486                mInput != null &&
8487                getSelectionStart() >= 0 &&
8488                getSelectionEnd() >= 0 &&
8489                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8490                hasPrimaryClip());
8491    }
8492
8493    private static long packRangeInLong(int start, int end) {
8494        return (((long) start) << 32) | end;
8495    }
8496
8497    private static int extractRangeStartFromLong(long range) {
8498        return (int) (range >>> 32);
8499    }
8500
8501    private static int extractRangeEndFromLong(long range) {
8502        return (int) (range & 0x00000000FFFFFFFFL);
8503    }
8504
8505    private boolean selectAll() {
8506        final int length = mText.length();
8507        Selection.setSelection((Spannable) mText, 0, length);
8508        return length > 0;
8509    }
8510
8511    /**
8512     * Adjusts selection to the word under last touch offset.
8513     * Return true if the operation was successfully performed.
8514     */
8515    private boolean selectCurrentWord() {
8516        if (!canSelectText()) {
8517            return false;
8518        }
8519
8520        if (hasPasswordTransformationMethod()) {
8521            // Always select all on a password field.
8522            // Cut/copy menu entries are not available for passwords, but being able to select all
8523            // is however useful to delete or paste to replace the entire content.
8524            return selectAll();
8525        }
8526
8527        int klass = mInputType & InputType.TYPE_MASK_CLASS;
8528        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
8529
8530        // Specific text field types: select the entire text for these
8531        if (klass == InputType.TYPE_CLASS_NUMBER ||
8532                klass == InputType.TYPE_CLASS_PHONE ||
8533                klass == InputType.TYPE_CLASS_DATETIME ||
8534                variation == InputType.TYPE_TEXT_VARIATION_URI ||
8535                variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
8536                variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
8537                variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
8538            return selectAll();
8539        }
8540
8541        long lastTouchOffsets = getLastTouchOffsets();
8542        final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
8543        final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
8544
8545        // Safety check in case standard touch event handling has been bypassed
8546        if (minOffset < 0 || minOffset >= mText.length()) return false;
8547        if (maxOffset < 0 || maxOffset >= mText.length()) return false;
8548
8549        int selectionStart, selectionEnd;
8550
8551        // If a URLSpan (web address, email, phone...) is found at that position, select it.
8552        URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
8553        if (urlSpans.length == 1) {
8554            URLSpan url = urlSpans[0];
8555            selectionStart = ((Spanned) mText).getSpanStart(url);
8556            selectionEnd = ((Spanned) mText).getSpanEnd(url);
8557        } else {
8558            WordIterator wordIterator = getWordIterator();
8559            // WordIterator handles text changes, this is a no-op if text in unchanged.
8560            wordIterator.setCharSequence(mText);
8561
8562            selectionStart = wordIterator.getBeginning(minOffset);
8563            if (selectionStart == BreakIterator.DONE) return false;
8564
8565            selectionEnd = wordIterator.getEnd(maxOffset);
8566            if (selectionEnd == BreakIterator.DONE) return false;
8567        }
8568
8569        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8570        return true;
8571    }
8572
8573    WordIterator getWordIterator() {
8574        if (mWordIterator == null) {
8575            mWordIterator = new WordIterator();
8576        }
8577        return mWordIterator;
8578    }
8579
8580    private SpellChecker getSpellChecker() {
8581        if (mSpellChecker == null) {
8582            mSpellChecker = new SpellChecker(this);
8583        }
8584        return mSpellChecker;
8585    }
8586
8587    private long getLastTouchOffsets() {
8588        int minOffset, maxOffset;
8589
8590        if (mContextMenuTriggeredByKey) {
8591            minOffset = getSelectionStart();
8592            maxOffset = getSelectionEnd();
8593        } else {
8594            SelectionModifierCursorController selectionController = getSelectionController();
8595            minOffset = selectionController.getMinTouchOffset();
8596            maxOffset = selectionController.getMaxTouchOffset();
8597        }
8598
8599        return packRangeInLong(minOffset, maxOffset);
8600    }
8601
8602    @Override
8603    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
8604        super.onPopulateAccessibilityEvent(event);
8605
8606        final boolean isPassword = hasPasswordTransformationMethod();
8607        if (!isPassword) {
8608            CharSequence text = getText();
8609            if (TextUtils.isEmpty(text)) {
8610                text = getHint();
8611            }
8612            if (TextUtils.isEmpty(text)) {
8613                text = getContentDescription();
8614            }
8615            if (!TextUtils.isEmpty(text)) {
8616                event.getText().add(text);
8617            }
8618        }
8619    }
8620
8621    @Override
8622    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8623        super.onInitializeAccessibilityEvent(event);
8624
8625        final boolean isPassword = hasPasswordTransformationMethod();
8626        event.setPassword(isPassword);
8627
8628        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8629            event.setFromIndex(Selection.getSelectionStart(mText));
8630            event.setToIndex(Selection.getSelectionEnd(mText));
8631            event.setItemCount(mText.length());
8632        }
8633    }
8634
8635    @Override
8636    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8637        super.onInitializeAccessibilityNodeInfo(info);
8638
8639        final boolean isPassword = hasPasswordTransformationMethod();
8640        if (!isPassword) {
8641            info.setText(getText());
8642        }
8643        info.setPassword(isPassword);
8644    }
8645
8646    @Override
8647    public void sendAccessibilityEvent(int eventType) {
8648        // Do not send scroll events since first they are not interesting for
8649        // accessibility and second such events a generated too frequently.
8650        // For details see the implementation of bringTextIntoView().
8651        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8652            return;
8653        }
8654        super.sendAccessibilityEvent(eventType);
8655    }
8656
8657    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8658            int fromIndex, int removedCount, int addedCount) {
8659        AccessibilityEvent event =
8660            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8661        event.setFromIndex(fromIndex);
8662        event.setRemovedCount(removedCount);
8663        event.setAddedCount(addedCount);
8664        event.setBeforeText(beforeText);
8665        sendAccessibilityEventUnchecked(event);
8666    }
8667
8668    @Override
8669    protected void onCreateContextMenu(ContextMenu menu) {
8670        super.onCreateContextMenu(menu);
8671        boolean added = false;
8672        mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
8673        // Problem with context menu on long press: the menu appears while the key in down and when
8674        // the key is released, the view does not receive the key_up event.
8675        // We need two layers of flags: mDPadCenterIsDown and mEnterKeyIsDown are set in key down/up
8676        // events. We cannot simply clear these flags in onTextContextMenuItem since
8677        // it may not be called (if the user/ discards the context menu with the back key).
8678        // We clear these flags here and mContextMenuTriggeredByKey saves that state so that it is
8679        // available in onTextContextMenuItem.
8680        mDPadCenterIsDown = mEnterKeyIsDown = false;
8681
8682        MenuHandler handler = new MenuHandler();
8683
8684        if (mText instanceof Spanned && hasSelectionController()) {
8685            long lastTouchOffset = getLastTouchOffsets();
8686            final int selStart = extractRangeStartFromLong(lastTouchOffset);
8687            final int selEnd = extractRangeEndFromLong(lastTouchOffset);
8688
8689            URLSpan[] urls = ((Spanned) mText).getSpans(selStart, selEnd, URLSpan.class);
8690            if (urls.length > 0) {
8691                menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
8692                        setOnMenuItemClickListener(handler);
8693
8694                added = true;
8695            }
8696        }
8697
8698        // The context menu is not empty, which will prevent the selection mode from starting.
8699        // Add a entry to start it in the context menu.
8700        // TODO Does not handle the case where a subclass does not call super.thisMethod or
8701        // populates the menu AFTER this call.
8702        if (menu.size() > 0) {
8703            menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode).
8704                    setOnMenuItemClickListener(handler);
8705            added = true;
8706        }
8707
8708        if (added) {
8709            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
8710        }
8711    }
8712
8713    /**
8714     * Returns whether this text view is a current input method target.  The
8715     * default implementation just checks with {@link InputMethodManager}.
8716     */
8717    public boolean isInputMethodTarget() {
8718        InputMethodManager imm = InputMethodManager.peekInstance();
8719        return imm != null && imm.isActive(this);
8720    }
8721
8722    // Selection context mode
8723    private static final int ID_SELECT_ALL = android.R.id.selectAll;
8724    private static final int ID_CUT = android.R.id.cut;
8725    private static final int ID_COPY = android.R.id.copy;
8726    private static final int ID_PASTE = android.R.id.paste;
8727    // Context menu entries
8728    private static final int ID_COPY_URL = android.R.id.copyUrl;
8729    private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
8730
8731    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
8732        public boolean onMenuItemClick(MenuItem item) {
8733            return onTextContextMenuItem(item.getItemId());
8734        }
8735    }
8736
8737    /**
8738     * Called when a context menu option for the text view is selected.  Currently
8739     * this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode},
8740     * {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut}
8741     * or {@link android.R.id#copy}.
8742     *
8743     * @return true if the context menu item action was performed.
8744     */
8745    public boolean onTextContextMenuItem(int id) {
8746        int min = 0;
8747        int max = mText.length();
8748
8749        if (isFocused()) {
8750            final int selStart = getSelectionStart();
8751            final int selEnd = getSelectionEnd();
8752
8753            min = Math.max(0, Math.min(selStart, selEnd));
8754            max = Math.max(0, Math.max(selStart, selEnd));
8755        }
8756
8757        switch (id) {
8758            case ID_COPY_URL:
8759                URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
8760                if (urls.length >= 1) {
8761                    ClipData clip = null;
8762                    for (int i=0; i<urls.length; i++) {
8763                        Uri uri = Uri.parse(urls[0].getURL());
8764                        if (clip == null) {
8765                            clip = ClipData.newRawUri(null, uri);
8766                        } else {
8767                            clip.addItem(new ClipData.Item(uri));
8768                        }
8769                    }
8770                    if (clip != null) {
8771                        setPrimaryClip(clip);
8772                    }
8773                }
8774                stopSelectionActionMode();
8775                return true;
8776
8777            case ID_SELECTION_MODE:
8778                if (mSelectionActionMode != null) {
8779                    // Selection mode is already started, simply change selected part.
8780                    selectCurrentWord();
8781                } else {
8782                    startSelectionActionMode();
8783                }
8784                return true;
8785
8786            case ID_SELECT_ALL:
8787                // This does not enter text selection mode. Text is highlighted, so that it can be
8788                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
8789                selectAll();
8790                return true;
8791
8792            case ID_PASTE:
8793                paste(min, max);
8794                return true;
8795
8796            case ID_CUT:
8797                setPrimaryClip(ClipData.newPlainText(null, mTransformed.subSequence(min, max)));
8798                ((Editable) mText).delete(min, max);
8799                stopSelectionActionMode();
8800                return true;
8801
8802            case ID_COPY:
8803                setPrimaryClip(ClipData.newPlainText(null, mTransformed.subSequence(min, max)));
8804                stopSelectionActionMode();
8805                return true;
8806        }
8807        return false;
8808    }
8809
8810    /**
8811     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8812     * by [min, max] when replacing this region by paste.
8813     * Note that if there were two spaces (or more) at that position before, they are kept. We just
8814     * make sure we do not add an extra one from the paste content.
8815     */
8816    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8817        if (paste.length() > 0) {
8818            if (min > 0) {
8819                final char charBefore = mTransformed.charAt(min - 1);
8820                final char charAfter = paste.charAt(0);
8821
8822                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8823                    // Two spaces at beginning of paste: remove one
8824                    final int originalLength = mText.length();
8825                    ((Editable) mText).delete(min - 1, min);
8826                    // Due to filters, there is no guarantee that exactly one character was
8827                    // removed: count instead.
8828                    final int delta = mText.length() - originalLength;
8829                    min += delta;
8830                    max += delta;
8831                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8832                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8833                    // No space at beginning of paste: add one
8834                    final int originalLength = mText.length();
8835                    ((Editable) mText).replace(min, min, " ");
8836                    // Taking possible filters into account as above.
8837                    final int delta = mText.length() - originalLength;
8838                    min += delta;
8839                    max += delta;
8840                }
8841            }
8842
8843            if (max < mText.length()) {
8844                final char charBefore = paste.charAt(paste.length() - 1);
8845                final char charAfter = mTransformed.charAt(max);
8846
8847                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8848                    // Two spaces at end of paste: remove one
8849                    ((Editable) mText).delete(max, max + 1);
8850                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8851                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8852                    // No space at end of paste: add one
8853                    ((Editable) mText).replace(max, max, " ");
8854                }
8855            }
8856        }
8857
8858        return packRangeInLong(min, max);
8859    }
8860
8861    private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
8862        TextView shadowView = (TextView) inflate(mContext,
8863                com.android.internal.R.layout.text_drag_thumbnail, null);
8864
8865        if (shadowView == null) {
8866            throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
8867        }
8868
8869        if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
8870            text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
8871        }
8872        shadowView.setText(text);
8873        shadowView.setTextColor(getTextColors());
8874
8875        shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge);
8876        shadowView.setGravity(Gravity.CENTER);
8877
8878        shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
8879                ViewGroup.LayoutParams.WRAP_CONTENT));
8880
8881        final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8882        shadowView.measure(size, size);
8883
8884        shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight());
8885        shadowView.invalidate();
8886        return new DragShadowBuilder(shadowView);
8887    }
8888
8889    private static class DragLocalState {
8890        public TextView sourceTextView;
8891        public int start, end;
8892
8893        public DragLocalState(TextView sourceTextView, int start, int end) {
8894            this.sourceTextView = sourceTextView;
8895            this.start = start;
8896            this.end = end;
8897        }
8898    }
8899
8900    @Override
8901    public boolean performLongClick() {
8902        if (super.performLongClick()) {
8903            mDiscardNextActionUp = true;
8904            return true;
8905        }
8906
8907        boolean handled = false;
8908
8909        // Long press in empty space moves cursor and shows the Paste affordance if available.
8910        if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
8911                mInsertionControllerEnabled) {
8912            final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
8913            stopSelectionActionMode();
8914            Selection.setSelection((Spannable) mText, offset);
8915            getInsertionController().showWithActionPopup();
8916            handled = true;
8917        }
8918
8919        if (!handled && mSelectionActionMode != null) {
8920            if (touchPositionIsInSelection()) {
8921                // Start a drag
8922                final int start = getSelectionStart();
8923                final int end = getSelectionEnd();
8924                CharSequence selectedText = mTransformed.subSequence(start, end);
8925                ClipData data = ClipData.newPlainText(null, selectedText);
8926                DragLocalState localState = new DragLocalState(this, start, end);
8927                startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
8928                stopSelectionActionMode();
8929            } else {
8930                selectCurrentWord();
8931                getSelectionController().show();
8932            }
8933            handled = true;
8934        }
8935
8936        // Start a new selection
8937        handled |= !handled && startSelectionActionMode();
8938
8939        if (handled) {
8940            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
8941            mDiscardNextActionUp = true;
8942        }
8943
8944        return handled;
8945    }
8946
8947    private boolean touchPositionIsInSelection() {
8948        int selectionStart = getSelectionStart();
8949        int selectionEnd = getSelectionEnd();
8950
8951        if (selectionStart == selectionEnd) {
8952            return false;
8953        }
8954
8955        if (selectionStart > selectionEnd) {
8956            int tmp = selectionStart;
8957            selectionStart = selectionEnd;
8958            selectionEnd = tmp;
8959            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8960        }
8961
8962        SelectionModifierCursorController selectionController = getSelectionController();
8963        int minOffset = selectionController.getMinTouchOffset();
8964        int maxOffset = selectionController.getMaxTouchOffset();
8965
8966        return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
8967    }
8968
8969    private PositionListener getPositionListener() {
8970        if (mPositionListener == null) {
8971            mPositionListener = new PositionListener();
8972        }
8973        return mPositionListener;
8974    }
8975
8976    private interface TextViewPositionListener {
8977        public void updatePosition(int parentPositionX, int parentPositionY, boolean modified);
8978    }
8979
8980    private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
8981        // 3 handles, 2 ActionPopup (suggestionsPopup first hides the others)
8982        private final int MAXIMUM_NUMBER_OF_LISTENERS = 5;
8983        private TextViewPositionListener[] mPositionListeners =
8984                new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
8985        private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
8986        private boolean mPositionHasChanged = true;
8987        // Absolute position of the TextView with respect to its parent window
8988        private int mPositionX, mPositionY;
8989        private int mNumberOfListeners;
8990
8991        public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
8992            if (mNumberOfListeners == 0) {
8993                updatePosition();
8994                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
8995                vto.addOnPreDrawListener(this);
8996            }
8997
8998            int emptySlotIndex = -1;
8999            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9000                TextViewPositionListener listener = mPositionListeners[i];
9001                if (listener == positionListener) {
9002                    return;
9003                } else if (emptySlotIndex < 0 && listener == null) {
9004                    emptySlotIndex = i;
9005                }
9006            }
9007
9008            mPositionListeners[emptySlotIndex] = positionListener;
9009            mCanMove[emptySlotIndex] = canMove;
9010            mNumberOfListeners++;
9011        }
9012
9013        public void removeSubscriber(TextViewPositionListener positionListener) {
9014            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9015                if (mPositionListeners[i] == positionListener) {
9016                    mPositionListeners[i] = null;
9017                    mNumberOfListeners--;
9018                    break;
9019                }
9020            }
9021
9022            if (mNumberOfListeners == 0) {
9023                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9024                vto.removeOnPreDrawListener(this);
9025            }
9026        }
9027
9028        public int getPositionX() {
9029            return mPositionX;
9030        }
9031
9032        public int getPositionY() {
9033            return mPositionY;
9034        }
9035
9036        @Override
9037        public boolean onPreDraw() {
9038            updatePosition();
9039
9040            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9041                if (mPositionHasChanged || mCanMove[i]) {
9042                    TextViewPositionListener positionListener = mPositionListeners[i];
9043                    if (positionListener != null) {
9044                        positionListener.updatePosition(mPositionX, mPositionY,
9045                                mPositionHasChanged);
9046                    }
9047                }
9048            }
9049
9050            return true;
9051        }
9052
9053        private void updatePosition() {
9054            TextView.this.getLocationInWindow(mTempCoords);
9055
9056            mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
9057
9058            mPositionX = mTempCoords[0];
9059            mPositionY = mTempCoords[1];
9060        }
9061
9062        public boolean isVisible(int positionX, int positionY) {
9063            final TextView textView = TextView.this;
9064
9065            if (mTempRect == null) mTempRect = new Rect();
9066            final Rect clip = mTempRect;
9067            clip.left = getCompoundPaddingLeft();
9068            clip.top = getExtendedPaddingTop();
9069            clip.right = textView.getWidth() - getCompoundPaddingRight();
9070            clip.bottom = textView.getHeight() - getExtendedPaddingBottom();
9071
9072            final ViewParent parent = textView.getParent();
9073            if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
9074                return false;
9075            }
9076
9077            int posX = mPositionX + positionX;
9078            int posY = mPositionY + positionY;
9079
9080            // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
9081            return posX >= clip.left - 1 && posX <= clip.right + 1 &&
9082                    posY >= clip.top && posY <= clip.bottom;
9083        }
9084
9085        public boolean isOffsetVisible(int offset) {
9086            final int line = mLayout.getLineForOffset(offset);
9087            final int lineBottom = mLayout.getLineBottom(line);
9088            final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
9089            return isVisible(primaryHorizontal, lineBottom);
9090        }
9091    }
9092
9093    private abstract class PinnedPopupWindow implements TextViewPositionListener {
9094        protected PopupWindow mPopupWindow;
9095        protected LinearLayout mContentView;
9096        int mPositionX, mPositionY;
9097
9098        protected abstract void createPopupWindow();
9099        protected abstract void initContentView();
9100        protected abstract int getTextOffset();
9101        protected abstract int getVerticalLocalPosition(int line);
9102        protected abstract int clipVertically(int positionY);
9103
9104        public PinnedPopupWindow() {
9105            createPopupWindow();
9106
9107            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
9108            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
9109            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
9110
9111            mContentView = new LinearLayout(TextView.this.getContext());
9112            LayoutParams wrapContent = new LayoutParams(
9113                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
9114            mContentView.setLayoutParams(wrapContent);
9115
9116            initContentView();
9117            mPopupWindow.setContentView(mContentView);
9118        }
9119
9120        public void show() {
9121            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9122            mContentView.measure(
9123                    View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
9124                            View.MeasureSpec.AT_MOST),
9125                    View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
9126                            View.MeasureSpec.AT_MOST));
9127
9128            TextView.this.getPositionListener().addSubscriber(this, false);
9129
9130            computeLocalPosition();
9131
9132            final PositionListener positionListener = TextView.this.getPositionListener();
9133            updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
9134        }
9135
9136        private void computeLocalPosition() {
9137            final int offset = getTextOffset();
9138
9139            final int width = mContentView.getMeasuredWidth();
9140            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
9141            mPositionX += viewportToContentHorizontalOffset();
9142
9143            final int line = mLayout.getLineForOffset(offset);
9144            mPositionY = getVerticalLocalPosition(line);
9145            mPositionY += viewportToContentVerticalOffset();
9146        }
9147
9148        private void updatePosition(int parentPositionX, int parentPositionY) {
9149            int positionX = parentPositionX + mPositionX;
9150            int positionY = parentPositionY + mPositionY;
9151
9152            positionY = clipVertically(positionY);
9153
9154            // Horizontal clipping
9155            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9156            final int width = mContentView.getMeasuredWidth();
9157            positionX = Math.min(displayMetrics.widthPixels - width, positionX);
9158            positionX = Math.max(0, positionX);
9159
9160            if (isShowing()) {
9161                mPopupWindow.update(positionX, positionY, -1, -1);
9162            } else {
9163                mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
9164                        positionX, positionY);
9165            }
9166        }
9167
9168        public void hide() {
9169            mPopupWindow.dismiss();
9170            TextView.this.getPositionListener().removeSubscriber(this);
9171        }
9172
9173        @Override
9174        public void updatePosition(int parentPositionX, int parentPositionY, boolean modified) {
9175            if (isShowing() && getPositionListener().isOffsetVisible(getTextOffset())) {
9176                updatePosition(parentPositionX, parentPositionY);
9177            } else {
9178                hide();
9179            }
9180        }
9181
9182        public boolean isShowing() {
9183            return mPopupWindow.isShowing();
9184        }
9185    }
9186
9187    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnClickListener {
9188        private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
9189        private static final int NO_SUGGESTIONS = -1;
9190        private static final float AVERAGE_HIGHLIGHTS_PER_SUGGESTION = 1.4f;
9191        private WordIterator mSuggestionWordIterator;
9192        private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan
9193                [(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)];
9194        private boolean mCursorWasVisibleBeforeSuggestions;
9195
9196        private class CustomPopupWindow extends PopupWindow {
9197            public CustomPopupWindow(Context context, int defStyle) {
9198                super(context, null, defStyle);
9199            }
9200
9201            @Override
9202            public void dismiss() {
9203                super.dismiss();
9204
9205                if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
9206                    ((Editable) mText).removeSpan(mSuggestionRangeSpan);
9207                }
9208
9209                setCursorVisible(mCursorWasVisibleBeforeSuggestions);
9210                if (hasInsertionController()) {
9211                    getInsertionController().show();
9212                }
9213            }
9214        }
9215
9216        public SuggestionsPopupWindow() {
9217            for (int i = 0; i < mHighlightSpans.length; i++) {
9218                mHighlightSpans[i] = new TextAppearanceSpan(mContext,
9219                        android.R.style.TextAppearance_SuggestionHighlight);
9220            }
9221            mCursorWasVisibleBeforeSuggestions = mCursorVisible;
9222        }
9223
9224        @Override
9225        protected void createPopupWindow() {
9226            mPopupWindow = new CustomPopupWindow(TextView.this.mContext,
9227                com.android.internal.R.attr.textSuggestionsWindowStyle);
9228            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
9229            mPopupWindow.setFocusable(true);
9230            mPopupWindow.setClippingEnabled(false);
9231        }
9232
9233        @Override
9234        protected void initContentView() {
9235            mContentView.setOrientation(LinearLayout.VERTICAL);
9236
9237            LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
9238                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9239
9240            if (inflater == null) {
9241                throw new IllegalArgumentException(
9242                        "Unable to create inflater for TextEdit suggestions");
9243            }
9244
9245            // Inflate the suggestion items once and for all.
9246            for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
9247                View childView = inflater.inflate(mTextEditSuggestionItemLayout,
9248                        mContentView, false);
9249
9250                if (! (childView instanceof TextView)) {
9251                    throw new IllegalArgumentException(
9252                            "Inflated TextEdit suggestion item is not a TextView: " + childView);
9253                }
9254
9255                childView.setTag(new SuggestionInfo());
9256                mContentView.addView(childView);
9257                childView.setOnClickListener(this);
9258            }
9259        }
9260
9261        private class SuggestionInfo {
9262            int suggestionStart, suggestionEnd; // range of suggestion item with replacement text
9263            int spanStart, spanEnd; // range in TextView where text should be inserted
9264            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
9265            int suggestionIndex; // the index of the suggestion inside suggestionSpan
9266        }
9267
9268        /**
9269         * Returns the suggestion spans that cover the current cursor position. The suggestion
9270         * spans are sorted according to the length of text that they are attached to.
9271         */
9272        private SuggestionSpan[] getSuggestionSpans() {
9273            int pos = TextView.this.getSelectionStart();
9274            Spannable spannable = (Spannable) TextView.this.mText;
9275            SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
9276
9277            // Cache the span length for performance reason.
9278            final HashMap<SuggestionSpan, Integer> spansLengths =
9279                    new HashMap<SuggestionSpan, Integer>();
9280
9281            for (SuggestionSpan suggestionSpan : suggestionSpans) {
9282                int start = spannable.getSpanStart(suggestionSpan);
9283                int end = spannable.getSpanEnd(suggestionSpan);
9284                spansLengths.put(suggestionSpan, Integer.valueOf(end - start));
9285            }
9286
9287            // The suggestions are sorted according to the lenght of the text that they cover
9288            // (shorter first)
9289            Arrays.sort(suggestionSpans, new Comparator<SuggestionSpan>() {
9290                public int compare(SuggestionSpan span1, SuggestionSpan span2) {
9291                    return spansLengths.get(span1).intValue() - spansLengths.get(span2).intValue();
9292                }
9293            });
9294
9295            return suggestionSpans;
9296        }
9297
9298        @Override
9299        public void show() {
9300            if (!(mText instanceof Editable)) return;
9301
9302            if (updateSuggestions()) {
9303                mCursorWasVisibleBeforeSuggestions = mCursorVisible;
9304                setCursorVisible(false);
9305                super.show();
9306            }
9307        }
9308
9309        @Override
9310        protected int getTextOffset() {
9311            return getSelectionStart();
9312        }
9313
9314        @Override
9315        protected int getVerticalLocalPosition(int line) {
9316            return mLayout.getLineBottom(line);
9317        }
9318
9319        @Override
9320        protected int clipVertically(int positionY) {
9321            final int height = mContentView.getMeasuredHeight();
9322            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9323            return Math.min(positionY, displayMetrics.heightPixels - height);
9324        }
9325
9326        @Override
9327        public void hide() {
9328            super.hide();
9329        }
9330
9331        private boolean updateSuggestions() {
9332            Spannable spannable = (Spannable)TextView.this.mText;
9333            SuggestionSpan[] suggestionSpans = getSuggestionSpans();
9334
9335            final int nbSpans = suggestionSpans.length;
9336
9337            int totalNbSuggestions = 0;
9338            int spanUnionStart = mText.length();
9339            int spanUnionEnd = 0;
9340
9341            for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
9342                SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
9343                final int spanStart = spannable.getSpanStart(suggestionSpan);
9344                final int spanEnd = spannable.getSpanEnd(suggestionSpan);
9345                spanUnionStart = Math.min(spanStart, spanUnionStart);
9346                spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
9347
9348                String[] suggestions = suggestionSpan.getSuggestions();
9349                int nbSuggestions = suggestions.length;
9350                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
9351                    TextView textView = (TextView) mContentView.getChildAt(
9352                            totalNbSuggestions);
9353                    textView.setText(suggestions[suggestionIndex]);
9354                    SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
9355                    suggestionInfo.spanStart = spanStart;
9356                    suggestionInfo.spanEnd = spanEnd;
9357                    suggestionInfo.suggestionSpan = suggestionSpan;
9358                    suggestionInfo.suggestionIndex = suggestionIndex;
9359
9360                    totalNbSuggestions++;
9361                    if (totalNbSuggestions == MAX_NUMBER_SUGGESTIONS) {
9362                        // Also end outer for loop
9363                        spanIndex = nbSpans;
9364                        break;
9365                    }
9366                }
9367            }
9368
9369            if (totalNbSuggestions == 0) return false;
9370
9371            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
9372            ((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
9373                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9374
9375            for (int i = 0; i < totalNbSuggestions; i++) {
9376                final TextView textView = (TextView) mContentView.getChildAt(i);
9377                highlightTextDifferences(textView, spanUnionStart, spanUnionEnd);
9378            }
9379
9380            for (int i = 0; i < totalNbSuggestions; i++) {
9381                mContentView.getChildAt(i).setVisibility(VISIBLE);
9382            }
9383            for (int i = totalNbSuggestions; i < MAX_NUMBER_SUGGESTIONS; i++) {
9384                mContentView.getChildAt(i).setVisibility(GONE);
9385            }
9386
9387            return true;
9388        }
9389
9390        private void onDictionarySuggestionsReceived(String[] suggestions) {
9391            if (suggestions.length == 0) {
9392                // TODO Actual implementation of this feature
9393                suggestions = new String[] {"Add to dictionary"};
9394            }
9395
9396            WordIterator wordIterator = getWordIterator();
9397            wordIterator.setCharSequence(mText);
9398
9399            final int pos = getSelectionStart();
9400            int wordStart = wordIterator.getBeginning(pos);
9401            int wordEnd = wordIterator.getEnd(pos);
9402
9403            SuggestionSpan suggestionSpan = new SuggestionSpan(getContext(), suggestions, 0);
9404            ((Editable) mText).setSpan(suggestionSpan, wordStart, wordEnd,
9405                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9406            show();
9407        }
9408
9409        private long[] getWordLimits(CharSequence text) {
9410            // TODO locale for mSuggestionWordIterator
9411            if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator();
9412            mSuggestionWordIterator.setCharSequence(text);
9413
9414            // First pass will simply count the number of words to be able to create an array
9415            // Not too expensive since previous break positions are cached by the BreakIterator
9416            int nbWords = 0;
9417            int position = mSuggestionWordIterator.following(0);
9418            while (position != BreakIterator.DONE) {
9419                nbWords++;
9420                position = mSuggestionWordIterator.following(position);
9421            }
9422
9423            int index = 0;
9424            long[] result = new long[nbWords];
9425
9426            position = mSuggestionWordIterator.following(0);
9427            while (position != BreakIterator.DONE) {
9428                int wordStart = mSuggestionWordIterator.getBeginning(position);
9429                result[index++] = packRangeInLong(wordStart, position);
9430                position = mSuggestionWordIterator.following(position);
9431            }
9432
9433            return result;
9434        }
9435
9436        private TextAppearanceSpan highlightSpan(int index) {
9437            final int length = mHighlightSpans.length;
9438            if (index < length) {
9439                return mHighlightSpans[index];
9440            }
9441
9442            // Assumes indexes are requested in sequence: simply append one more item
9443            TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1];
9444            System.arraycopy(mHighlightSpans, 0, newArray, 0, length);
9445            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
9446                    android.R.style.TextAppearance_SuggestionHighlight);
9447            newArray[length] = highlightSpan;
9448            mHighlightSpans = newArray;
9449            return highlightSpan;
9450        }
9451
9452        private void highlightTextDifferences(TextView textView, int unionStart, int unionEnd) {
9453            SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
9454            final int spanStart = suggestionInfo.spanStart;
9455            final int spanEnd = suggestionInfo.spanEnd;
9456
9457            // Remove all text formating by converting to Strings
9458            final String text = textView.getText().toString();
9459            final String sourceText = mText.subSequence(spanStart, spanEnd).toString();
9460
9461            long[] sourceWordLimits = getWordLimits(sourceText);
9462            long[] wordLimits = getWordLimits(text);
9463
9464            SpannableStringBuilder ssb = new SpannableStringBuilder();
9465            // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd]
9466            // The final result is made of 3 parts: the text before, between and after the span
9467            // This is the text before, provided for context
9468            ssb.append(mText.subSequence(unionStart, spanStart).toString());
9469
9470            // shift is used to offset spans positions wrt span's beginning
9471            final int shift = spanStart - unionStart;
9472            suggestionInfo.suggestionStart = shift;
9473            suggestionInfo.suggestionEnd = shift + text.length();
9474
9475            // This is the actual suggestion text, which will be highlighted by the following code
9476            ssb.append(text);
9477
9478            String[] words = new String[wordLimits.length];
9479            for (int i = 0; i < wordLimits.length; i++) {
9480                int wordStart = extractRangeStartFromLong(wordLimits[i]);
9481                int wordEnd = extractRangeEndFromLong(wordLimits[i]);
9482                words[i] = text.substring(wordStart, wordEnd);
9483            }
9484
9485            // Highlighted word algorithm is based on word matching between source and text
9486            // Matching words are found from left to right. TODO: change for RTL languages
9487            // Characters between matching words are highlighted
9488            int previousCommonWordIndex = -1;
9489            int nbHighlightSpans = 0;
9490            for (int i = 0; i < sourceWordLimits.length; i++) {
9491                int wordStart = extractRangeStartFromLong(sourceWordLimits[i]);
9492                int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]);
9493                String sourceWord = sourceText.substring(wordStart, wordEnd);
9494
9495                for (int j = previousCommonWordIndex + 1; j < words.length; j++) {
9496                    if (sourceWord.equals(words[j])) {
9497                        if (j != previousCommonWordIndex + 1) {
9498                            int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
9499                                extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
9500                            int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]);
9501                            ssb.setSpan(highlightSpan(nbHighlightSpans++),
9502                                    shift + firstDifferentPosition, shift + lastDifferentPosition,
9503                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9504                        } else {
9505                            // Compare characters between words
9506                            int previousSourceWordEnd = i == 0 ? 0 :
9507                                extractRangeEndFromLong(sourceWordLimits[i - 1]);
9508                            int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]);
9509                            String sourceSpaces = sourceText.substring(previousSourceWordEnd,
9510                                    sourceWordStart);
9511
9512                            int previousWordEnd = j == 0 ? 0 :
9513                                extractRangeEndFromLong(wordLimits[j - 1]);
9514                            int currentWordStart = extractRangeStartFromLong(wordLimits[j]);
9515                            String textSpaces = text.substring(previousWordEnd, currentWordStart);
9516
9517                            if (!sourceSpaces.equals(textSpaces)) {
9518                                ssb.setSpan(highlightSpan(nbHighlightSpans++),
9519                                        shift + previousWordEnd, shift + currentWordStart,
9520                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9521                            }
9522                        }
9523                        previousCommonWordIndex = j;
9524                        break;
9525                    }
9526                }
9527            }
9528
9529            // Finally, compare ends of Strings
9530            if (previousCommonWordIndex < words.length - 1) {
9531                int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
9532                    extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
9533                int lastDifferentPosition = textView.length();
9534                ssb.setSpan(highlightSpan(nbHighlightSpans++),
9535                        shift + firstDifferentPosition, shift + lastDifferentPosition,
9536                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9537            } else {
9538                int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 :
9539                    extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]);
9540                String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length());
9541
9542                int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 :
9543                    extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
9544                String textSpaces = text.substring(lastCommonTextWordEnd, textView.length());
9545
9546                if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) {
9547                    ssb.setSpan(highlightSpan(nbHighlightSpans++),
9548                            shift + lastCommonTextWordEnd, shift + textView.length(),
9549                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
9550                }
9551            }
9552
9553            // Final part, text after the current suggestion range.
9554            ssb.append(mText.subSequence(spanEnd, unionEnd).toString());
9555            textView.setText(ssb);
9556        }
9557
9558        @Override
9559        public void onClick(View view) {
9560            if (view instanceof TextView) {
9561                TextView textView = (TextView) view;
9562                SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
9563                final int spanStart = suggestionInfo.spanStart;
9564                final int spanEnd = suggestionInfo.spanEnd;
9565                if (spanStart != NO_SUGGESTIONS) {
9566                    // SuggestionSpans are removed by replace: save them before
9567                    Editable editable = (Editable) mText;
9568                    SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
9569                            SuggestionSpan.class);
9570                    final int length = suggestionSpans.length;
9571                    int[] suggestionSpansStarts = new int[length];
9572                    int[] suggestionSpansEnds = new int[length];
9573                    int[] suggestionSpansFlags = new int[length];
9574                    for (int i = 0; i < length; i++) {
9575                        final SuggestionSpan suggestionSpan = suggestionSpans[i];
9576                        suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
9577                        suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
9578                        suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
9579                    }
9580
9581                    final int suggestionStart = suggestionInfo.suggestionStart;
9582                    final int suggestionEnd = suggestionInfo.suggestionEnd;
9583                    final String suggestion = textView.getText().subSequence(
9584                            suggestionStart, suggestionEnd).toString();
9585                    final String originalText = mText.subSequence(spanStart, spanEnd).toString();
9586                    editable.replace(spanStart, spanEnd, suggestion);
9587
9588                    // A replacement on a misspelled text removes the misspelled flag.
9589                    // TODO restore the flag if the misspelled word is selected back?
9590                    int suggestionSpanFlags = suggestionInfo.suggestionSpan.getFlags();
9591                    if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
9592                        suggestionSpanFlags &= ~(SuggestionSpan.FLAG_MISSPELLED);
9593                        suggestionSpanFlags &= ~(SuggestionSpan.FLAG_EASY_CORRECT);
9594                        suggestionInfo.suggestionSpan.setFlags(suggestionSpanFlags);
9595                    }
9596
9597                    // Notify source IME of the suggestion pick. Do this before swaping texts.
9598                    if (!TextUtils.isEmpty(
9599                            suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
9600                        InputMethodManager imm = InputMethodManager.peekInstance();
9601                        imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
9602                                suggestionInfo.suggestionIndex);
9603                    }
9604
9605                    // Swap text content between actual text and Suggestion span
9606                    String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
9607                    suggestions[suggestionInfo.suggestionIndex] = originalText;
9608
9609                    // Restore previous SuggestionSpans
9610                    final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
9611                    for (int i = 0; i < length; i++) {
9612                        // Only spans that include the modified region make sense after replacement
9613                        // Spans partially included in the replaced region are removed, there is no
9614                        // way to assign them a valid range after replacement
9615                        if (suggestionSpansStarts[i] <= spanStart &&
9616                                suggestionSpansEnds[i] >= spanEnd) {
9617                            editable.setSpan(suggestionSpans[i], suggestionSpansStarts[i],
9618                                    suggestionSpansEnds[i] + lengthDifference,
9619                                    suggestionSpansFlags[i]);
9620                        }
9621                    }
9622                }
9623            }
9624            hide();
9625        }
9626    }
9627
9628    void showSuggestions() {
9629        if (!isSuggestionsEnabled() || !isTextEditable()) return;
9630
9631        if (mSuggestionsPopupWindow == null) {
9632            mSuggestionsPopupWindow = new SuggestionsPopupWindow();
9633        }
9634        hideControllers();
9635        mSuggestionsPopupWindow.show();
9636    }
9637
9638    boolean areSuggestionsShown() {
9639        return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
9640    }
9641
9642    void onDictionarySuggestionsReceived(String[] suggestions) {
9643        if (mSuggestionsPopupWindow != null) {
9644            mSuggestionsPopupWindow.onDictionarySuggestionsReceived(suggestions);
9645        }
9646    }
9647
9648    /**
9649     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9650     * by the IME or by the spell checker as the user types. This is done by adding
9651     * {@link SuggestionSpan}s to the text.
9652     *
9653     * When suggestions are enabled (default), this list of suggestions will be displayed when the
9654     * user asks for them on these parts of the text. This value depends on the inputType of this
9655     * TextView.
9656     *
9657     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9658     *
9659     * In addition, the type variation must be one of
9660     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9661     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9662     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9663     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9664     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9665     *
9666     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9667     *
9668     * @return true if the suggestions popup window is enabled, based on the inputType.
9669     */
9670    public boolean isSuggestionsEnabled() {
9671        if ((mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
9672        if ((mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9673
9674        final int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
9675        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9676                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9677                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9678                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9679                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9680    }
9681
9682    /**
9683     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9684     * selection is initiated in this View.
9685     *
9686     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
9687     * Paste actions, depending on what this View supports.
9688     *
9689     * A custom implementation can add new entries in the default menu in its
9690     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
9691     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
9692     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
9693     * or {@link android.R.id#paste} ids as parameters.
9694     *
9695     * Returning false from
9696     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
9697     * the action mode from being started.
9698     *
9699     * Action click events should be handled by the custom implementation of
9700     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
9701     *
9702     * Note that text selection mode is not started when a TextView receives focus and the
9703     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9704     * that case, to allow for quick replacement.
9705     */
9706    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9707        mCustomSelectionActionModeCallback = actionModeCallback;
9708    }
9709
9710    /**
9711     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9712     *
9713     * @return The current custom selection callback.
9714     */
9715    public ActionMode.Callback getCustomSelectionActionModeCallback() {
9716        return mCustomSelectionActionModeCallback;
9717    }
9718
9719    /**
9720     *
9721     * @return true if the selection mode was actually started.
9722     */
9723    private boolean startSelectionActionMode() {
9724        if (mSelectionActionMode != null) {
9725            // Selection action mode is already started
9726            return false;
9727        }
9728
9729        if (!canSelectText() || !requestFocus()) {
9730            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
9731            return false;
9732        }
9733
9734        if (!hasSelection()) {
9735            // There may already be a selection on device rotation
9736            boolean currentWordSelected = selectCurrentWord();
9737            if (!currentWordSelected) {
9738                // No word found under cursor or text selection not permitted.
9739                return false;
9740            }
9741        }
9742
9743        ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
9744        mSelectionActionMode = startActionMode(actionModeCallback);
9745        final boolean selectionStarted = mSelectionActionMode != null;
9746
9747        if (selectionStarted && !mTextIsSelectable) {
9748            // Show the IME to be able to replace text, except when selecting non editable text.
9749            final InputMethodManager imm = InputMethodManager.peekInstance();
9750            if (imm != null) imm.showSoftInput(this, 0, null);
9751        }
9752
9753        return selectionStarted;
9754    }
9755
9756    private void stopSelectionActionMode() {
9757        if (mSelectionActionMode != null) {
9758            // This will hide the mSelectionModifierCursorController
9759            mSelectionActionMode.finish();
9760        }
9761    }
9762
9763    /**
9764     * Paste clipboard content between min and max positions.
9765     */
9766    private void paste(int min, int max) {
9767        ClipboardManager clipboard =
9768            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9769        ClipData clip = clipboard.getPrimaryClip();
9770        if (clip != null) {
9771            boolean didFirst = false;
9772            for (int i=0; i<clip.getItemCount(); i++) {
9773                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
9774                if (paste != null) {
9775                    if (!didFirst) {
9776                        long minMax = prepareSpacesAroundPaste(min, max, paste);
9777                        min = extractRangeStartFromLong(minMax);
9778                        max = extractRangeEndFromLong(minMax);
9779                        Selection.setSelection((Spannable) mText, max);
9780                        ((Editable) mText).replace(min, max, paste);
9781                        didFirst = true;
9782                    } else {
9783                        ((Editable) mText).insert(getSelectionEnd(), "\n");
9784                        ((Editable) mText).insert(getSelectionEnd(), paste);
9785                    }
9786                }
9787            }
9788            stopSelectionActionMode();
9789            sLastCutOrCopyTime = 0;
9790        }
9791    }
9792
9793    private void setPrimaryClip(ClipData clip) {
9794        ClipboardManager clipboard = (ClipboardManager) getContext().
9795                getSystemService(Context.CLIPBOARD_SERVICE);
9796        clipboard.setPrimaryClip(clip);
9797        sLastCutOrCopyTime = SystemClock.uptimeMillis();
9798    }
9799
9800    /**
9801     * An ActionMode Callback class that is used to provide actions while in text selection mode.
9802     *
9803     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
9804     * on which of these this TextView supports.
9805     */
9806    private class SelectionActionModeCallback implements ActionMode.Callback {
9807
9808        @Override
9809        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
9810            TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
9811
9812            boolean allowText = getContext().getResources().getBoolean(
9813                    com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
9814
9815            mode.setTitle(allowText ?
9816                    mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
9817            mode.setSubtitle(null);
9818
9819            int selectAllIconId = 0; // No icon by default
9820            if (!allowText) {
9821                // Provide an icon, text will not be displayed on smaller screens.
9822                selectAllIconId = styledAttributes.getResourceId(
9823                        R.styleable.Theme_actionModeSelectAllDrawable, 0);
9824            }
9825
9826            menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
9827                    setIcon(selectAllIconId).
9828                    setAlphabeticShortcut('a').
9829                    setShowAsAction(
9830                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
9831
9832            if (canCut()) {
9833                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
9834                    setIcon(styledAttributes.getResourceId(
9835                            R.styleable.Theme_actionModeCutDrawable, 0)).
9836                    setAlphabeticShortcut('x').
9837                    setShowAsAction(
9838                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
9839            }
9840
9841            if (canCopy()) {
9842                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
9843                    setIcon(styledAttributes.getResourceId(
9844                            R.styleable.Theme_actionModeCopyDrawable, 0)).
9845                    setAlphabeticShortcut('c').
9846                    setShowAsAction(
9847                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
9848            }
9849
9850            if (canPaste()) {
9851                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
9852                        setIcon(styledAttributes.getResourceId(
9853                                R.styleable.Theme_actionModePasteDrawable, 0)).
9854                        setAlphabeticShortcut('v').
9855                        setShowAsAction(
9856                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
9857            }
9858
9859            styledAttributes.recycle();
9860
9861            if (mCustomSelectionActionModeCallback != null) {
9862                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
9863                    // The custom mode can choose to cancel the action mode
9864                    return false;
9865                }
9866            }
9867
9868            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
9869                getSelectionController().show();
9870                return true;
9871            } else {
9872                return false;
9873            }
9874        }
9875
9876        @Override
9877        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
9878            if (mCustomSelectionActionModeCallback != null) {
9879                return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
9880            }
9881            return true;
9882        }
9883
9884        @Override
9885        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
9886            if (mCustomSelectionActionModeCallback != null &&
9887                 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
9888                return true;
9889            }
9890            return onTextContextMenuItem(item.getItemId());
9891        }
9892
9893        @Override
9894        public void onDestroyActionMode(ActionMode mode) {
9895            if (mCustomSelectionActionModeCallback != null) {
9896                mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
9897            }
9898            Selection.setSelection((Spannable) mText, getSelectionEnd());
9899
9900            if (mSelectionModifierCursorController != null) {
9901                mSelectionModifierCursorController.hide();
9902            }
9903
9904            mSelectionActionMode = null;
9905        }
9906    }
9907
9908    private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
9909        private static final int POPUP_TEXT_LAYOUT =
9910                com.android.internal.R.layout.text_edit_action_popup_text;
9911        private TextView mPasteTextView;
9912        private TextView mReplaceTextView;
9913
9914        @Override
9915        protected void createPopupWindow() {
9916            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
9917                    com.android.internal.R.attr.textSelectHandleWindowStyle);
9918            mPopupWindow.setClippingEnabled(true);
9919        }
9920
9921        @Override
9922        protected void initContentView() {
9923            mContentView.setOrientation(LinearLayout.HORIZONTAL);
9924            mContentView.setBackgroundResource(
9925                    com.android.internal.R.drawable.text_edit_side_paste_window);
9926
9927            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
9928                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9929
9930            LayoutParams wrapContent = new LayoutParams(
9931                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
9932
9933            mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
9934            mPasteTextView.setLayoutParams(wrapContent);
9935            mContentView.addView(mPasteTextView);
9936            mPasteTextView.setText(com.android.internal.R.string.paste);
9937            mPasteTextView.setOnClickListener(this);
9938
9939            mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
9940            mReplaceTextView.setLayoutParams(wrapContent);
9941            mContentView.addView(mReplaceTextView);
9942            mReplaceTextView.setText(com.android.internal.R.string.replace);
9943            mReplaceTextView.setOnClickListener(this);
9944        }
9945
9946        @Override
9947        public void show() {
9948            boolean canPaste = canPaste();
9949            boolean canSuggest = isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
9950            mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE);
9951            mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE);
9952
9953            if (!canPaste && !canSuggest) return;
9954
9955            super.show();
9956        }
9957
9958        @Override
9959        public void onClick(View view) {
9960            if (view == mPasteTextView && canPaste()) {
9961                onTextContextMenuItem(ID_PASTE);
9962                hide();
9963            } else if (view == mReplaceTextView) {
9964                final int middle = (getSelectionStart() + getSelectionEnd()) / 2;
9965                stopSelectionActionMode();
9966                Selection.setSelection((Spannable) mText, middle);
9967                showSuggestions();
9968            }
9969        }
9970
9971        @Override
9972        protected int getTextOffset() {
9973            return (getSelectionStart() + getSelectionEnd()) / 2;
9974        }
9975
9976        @Override
9977        protected int getVerticalLocalPosition(int line) {
9978            return mLayout.getLineTop(line) - mContentView.getMeasuredHeight();
9979        }
9980
9981        @Override
9982        protected int clipVertically(int positionY) {
9983            if (positionY < 0) {
9984                final int offset = getTextOffset();
9985                final int line = mLayout.getLineForOffset(offset);
9986                positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line);
9987                positionY += mContentView.getMeasuredHeight();
9988
9989                // Assumes insertion and selection handles share the same height
9990                final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
9991                positionY += handle.getIntrinsicHeight();
9992            }
9993
9994            return positionY;
9995        }
9996    }
9997
9998    private abstract class HandleView extends View implements TextViewPositionListener {
9999        protected Drawable mDrawable;
10000        private final PopupWindow mContainer;
10001        // Position with respect to the parent TextView
10002        private int mPositionX, mPositionY;
10003        private boolean mIsDragging;
10004        // Offset from touch position to mPosition
10005        private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
10006        protected int mHotspotX;
10007        // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
10008        private float mTouchOffsetY;
10009        // Where the touch position should be on the handle to ensure a maximum cursor visibility
10010        private float mIdealVerticalOffset;
10011        // Parent's (TextView) previous position in window
10012        private int mLastParentX, mLastParentY;
10013        // Transient action popup window for Paste and Replace actions
10014        protected ActionPopupWindow mActionPopupWindow;
10015        // Previous text character offset
10016        private int mPreviousOffset = -1;
10017        // Previous text character offset
10018        private boolean mPositionHasChanged = true;
10019        // Used to delay the appearance of the action popup window
10020        private Runnable mActionPopupShower;
10021
10022        public HandleView() {
10023            super(TextView.this.mContext);
10024            mContainer = new PopupWindow(TextView.this.mContext, null,
10025                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10026            mContainer.setSplitTouchEnabled(true);
10027            mContainer.setClippingEnabled(false);
10028            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
10029            mContainer.setContentView(this);
10030
10031            initDrawable();
10032
10033            final int handleHeight = mDrawable.getIntrinsicHeight();
10034            mTouchOffsetY = -0.3f * handleHeight;
10035            mIdealVerticalOffset = 0.7f * handleHeight;
10036        }
10037
10038        protected abstract void initDrawable();
10039
10040        // Touch-up filter: number of previous positions remembered
10041        private static final int HISTORY_SIZE = 5;
10042        private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
10043        private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
10044        private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
10045        private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
10046        private int mPreviousOffsetIndex = 0;
10047        private int mNumberPreviousOffsets = 0;
10048
10049        private void startTouchUpFilter(int offset) {
10050            mNumberPreviousOffsets = 0;
10051            addPositionToTouchUpFilter(offset);
10052        }
10053
10054        private void addPositionToTouchUpFilter(int offset) {
10055            mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
10056            mPreviousOffsets[mPreviousOffsetIndex] = offset;
10057            mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
10058            mNumberPreviousOffsets++;
10059        }
10060
10061        private void filterOnTouchUp() {
10062            final long now = SystemClock.uptimeMillis();
10063            int i = 0;
10064            int index = mPreviousOffsetIndex;
10065            final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
10066            while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
10067                i++;
10068                index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
10069            }
10070
10071            if (i > 0 && i < iMax &&
10072                    (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
10073                positionAtCursorOffset(mPreviousOffsets[index]);
10074            }
10075        }
10076
10077        public boolean offsetHasBeenChanged() {
10078            return mNumberPreviousOffsets > 1;
10079        }
10080
10081        @Override
10082        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
10083            setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
10084        }
10085
10086        public void show() {
10087            if (isShowing()) return;
10088
10089            getPositionListener().addSubscriber(this, true);
10090
10091            // Make sure the offset is always considered new, even when focusing at same position
10092            mPreviousOffset = -1;
10093            positionAtCursorOffset(getCurrentCursorOffset());
10094
10095            hideActionPopupWindow();
10096        }
10097
10098        protected void dismiss() {
10099            mIsDragging = false;
10100            mContainer.dismiss();
10101            onDetached();
10102        }
10103
10104        public void hide() {
10105            dismiss();
10106
10107            TextView.this.getPositionListener().removeSubscriber(this);
10108        }
10109
10110        void showActionPopupWindow(int delay) {
10111            if (mActionPopupWindow == null) {
10112                mActionPopupWindow = new ActionPopupWindow();
10113            }
10114            if (mActionPopupShower == null) {
10115                mActionPopupShower = new Runnable() {
10116                    public void run() {
10117                        mActionPopupWindow.show();
10118                    }
10119                };
10120            } else {
10121                TextView.this.removeCallbacks(mActionPopupShower);
10122            }
10123            TextView.this.postDelayed(mActionPopupShower, delay);
10124        }
10125
10126        protected void hideActionPopupWindow() {
10127            if (mActionPopupShower != null) {
10128                TextView.this.removeCallbacks(mActionPopupShower);
10129            }
10130            if (mActionPopupWindow != null) {
10131                mActionPopupWindow.hide();
10132            }
10133        }
10134
10135        public boolean isShowing() {
10136            return mContainer.isShowing();
10137        }
10138
10139        private boolean isVisible() {
10140            // Always show a dragging handle.
10141            if (mIsDragging) {
10142                return true;
10143            }
10144
10145            if (isInBatchEditMode()) {
10146                return false;
10147            }
10148
10149            return getPositionListener().isVisible(mPositionX + mHotspotX, mPositionY);
10150        }
10151
10152        public abstract int getCurrentCursorOffset();
10153
10154        public abstract void updateSelection(int offset);
10155
10156        public abstract void updatePosition(float x, float y);
10157
10158        protected void positionAtCursorOffset(int offset) {
10159            // A HandleView relies on the layout, which may be nulled by external methods
10160            if (mLayout == null) {
10161                // Will update controllers' state, hiding them and stopping selection mode if needed
10162                prepareCursorControllers();
10163                return;
10164            }
10165
10166            if (offset != mPreviousOffset) {
10167                updateSelection(offset);
10168                addPositionToTouchUpFilter(offset);
10169                final int line = mLayout.getLineForOffset(offset);
10170
10171                mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
10172                mPositionY = mLayout.getLineBottom(line);
10173
10174                // Take TextView's padding into account.
10175                mPositionX += viewportToContentHorizontalOffset();
10176                mPositionY += viewportToContentVerticalOffset();
10177
10178                mPreviousOffset = offset;
10179                mPositionHasChanged = true;
10180            }
10181        }
10182
10183        public void updatePosition(int parentPositionX, int parentPositionY, boolean modified) {
10184            positionAtCursorOffset(getCurrentCursorOffset());
10185            if (modified || mPositionHasChanged) {
10186                if (mIsDragging) {
10187                    // Update touchToWindow offset in case of parent scrolling while dragging
10188                    if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
10189                        mTouchToWindowOffsetX += parentPositionX - mLastParentX;
10190                        mTouchToWindowOffsetY += parentPositionY - mLastParentY;
10191                        mLastParentX = parentPositionX;
10192                        mLastParentY = parentPositionY;
10193                    }
10194
10195                    onHandleMoved();
10196                }
10197
10198                if (isVisible()) {
10199                    final int positionX = parentPositionX + mPositionX;
10200                    final int positionY = parentPositionY + mPositionY;
10201                    if (isShowing()) {
10202                        mContainer.update(positionX, positionY, -1, -1);
10203                    } else {
10204                        mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
10205                                positionX, positionY);
10206                    }
10207                } else {
10208                    if (isShowing()) {
10209                        dismiss();
10210                    }
10211                }
10212
10213                mPositionHasChanged = false;
10214            }
10215        }
10216
10217        @Override
10218        protected void onDraw(Canvas c) {
10219            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
10220            mDrawable.draw(c);
10221        }
10222
10223        @Override
10224        public boolean onTouchEvent(MotionEvent ev) {
10225            switch (ev.getActionMasked()) {
10226                case MotionEvent.ACTION_DOWN: {
10227                    startTouchUpFilter(getCurrentCursorOffset());
10228                    mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
10229                    mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
10230
10231                    final PositionListener positionListener = getPositionListener();
10232                    mLastParentX = positionListener.getPositionX();
10233                    mLastParentY = positionListener.getPositionY();
10234                    mIsDragging = true;
10235                    break;
10236                }
10237
10238                case MotionEvent.ACTION_MOVE: {
10239                    final float rawX = ev.getRawX();
10240                    final float rawY = ev.getRawY();
10241
10242                    // Vertical hysteresis: vertical down movement tends to snap to ideal offset
10243                    final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
10244                    final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
10245                    float newVerticalOffset;
10246                    if (previousVerticalOffset < mIdealVerticalOffset) {
10247                        newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
10248                        newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
10249                    } else {
10250                        newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
10251                        newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
10252                    }
10253                    mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
10254
10255                    final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
10256                    final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
10257
10258                    updatePosition(newPosX, newPosY);
10259                    break;
10260                }
10261
10262                case MotionEvent.ACTION_UP:
10263                    filterOnTouchUp();
10264                    mIsDragging = false;
10265                    break;
10266
10267                case MotionEvent.ACTION_CANCEL:
10268                    mIsDragging = false;
10269                    break;
10270            }
10271            return true;
10272        }
10273
10274        public boolean isDragging() {
10275            return mIsDragging;
10276        }
10277
10278        void onHandleMoved() {
10279            hideActionPopupWindow();
10280        }
10281
10282        public void onDetached() {
10283            hideActionPopupWindow();
10284        }
10285    }
10286
10287    private class InsertionHandleView extends HandleView {
10288        private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
10289        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
10290
10291        // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow
10292        private float mDownPositionX, mDownPositionY;
10293        private Runnable mHider;
10294
10295        @Override
10296        public void show() {
10297            super.show();
10298
10299            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
10300            if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
10301                showActionPopupWindow(0);
10302            }
10303
10304            hideAfterDelay();
10305        }
10306
10307        public void showWithActionPopup() {
10308            show();
10309            showActionPopupWindow(0);
10310        }
10311
10312        private void hideAfterDelay() {
10313            removeHiderCallback();
10314            if (mHider == null) {
10315                mHider = new Runnable() {
10316                    public void run() {
10317                        hide();
10318                    }
10319                };
10320            }
10321            TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
10322        }
10323
10324        private void removeHiderCallback() {
10325            if (mHider != null) {
10326                TextView.this.removeCallbacks(mHider);
10327            }
10328        }
10329
10330        @Override
10331        protected void initDrawable() {
10332            if (mSelectHandleCenter == null) {
10333                mSelectHandleCenter = mContext.getResources().getDrawable(
10334                        mTextSelectHandleRes);
10335            }
10336            mDrawable = mSelectHandleCenter;
10337            mHotspotX = mDrawable.getIntrinsicWidth() / 2;
10338        }
10339
10340        @Override
10341        public boolean onTouchEvent(MotionEvent ev) {
10342            final boolean result = super.onTouchEvent(ev);
10343
10344            switch (ev.getActionMasked()) {
10345                case MotionEvent.ACTION_DOWN:
10346                    mDownPositionX = ev.getRawX();
10347                    mDownPositionY = ev.getRawY();
10348                    break;
10349
10350                case MotionEvent.ACTION_UP:
10351                    if (!offsetHasBeenChanged()) {
10352                        final float deltaX = mDownPositionX - ev.getRawX();
10353                        final float deltaY = mDownPositionY - ev.getRawY();
10354                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
10355                        if (distanceSquared < mSquaredTouchSlopDistance) {
10356                            if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
10357                                // Tapping on the handle dismisses the displayed action popup
10358                                mActionPopupWindow.hide();
10359                            } else {
10360                                showWithActionPopup();
10361                            }
10362                        }
10363                    }
10364                    hideAfterDelay();
10365                    break;
10366
10367                case MotionEvent.ACTION_CANCEL:
10368                    hideAfterDelay();
10369                    break;
10370
10371                default:
10372                    break;
10373            }
10374
10375            return result;
10376        }
10377
10378        @Override
10379        public int getCurrentCursorOffset() {
10380            return TextView.this.getSelectionStart();
10381        }
10382
10383        @Override
10384        public void updateSelection(int offset) {
10385            Selection.setSelection((Spannable) mText, offset);
10386        }
10387
10388        @Override
10389        public void updatePosition(float x, float y) {
10390            positionAtCursorOffset(getOffsetForPosition(x, y));
10391        }
10392
10393        @Override
10394        void onHandleMoved() {
10395            super.onHandleMoved();
10396            removeHiderCallback();
10397        }
10398
10399        @Override
10400        public void onDetached() {
10401            super.onDetached();
10402            removeHiderCallback();
10403        }
10404    }
10405
10406    private class SelectionStartHandleView extends HandleView {
10407        @Override
10408        protected void initDrawable() {
10409            if (mSelectHandleLeft == null) {
10410                mSelectHandleLeft = mContext.getResources().getDrawable(
10411                        mTextSelectHandleLeftRes);
10412            }
10413            mDrawable = mSelectHandleLeft;
10414            mHotspotX = (mDrawable.getIntrinsicWidth() * 3) / 4;
10415        }
10416
10417        @Override
10418        public int getCurrentCursorOffset() {
10419            return TextView.this.getSelectionStart();
10420        }
10421
10422        @Override
10423        public void updateSelection(int offset) {
10424            Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
10425        }
10426
10427        @Override
10428        public void updatePosition(float x, float y) {
10429            final int selectionStart = getSelectionStart();
10430            final int selectionEnd = getSelectionEnd();
10431
10432            int offset = getOffsetForPosition(x, y);
10433
10434            // No need to redraw when the offset is unchanged
10435            if (offset == selectionStart) return;
10436            // Handles can not cross and selection is at least one character
10437            if (offset >= selectionEnd) offset = selectionEnd - 1;
10438
10439            positionAtCursorOffset(offset);
10440        }
10441
10442        public ActionPopupWindow getActionPopupWindow() {
10443            return mActionPopupWindow;
10444        }
10445    }
10446
10447    private class SelectionEndHandleView extends HandleView {
10448        @Override
10449        protected void initDrawable() {
10450            if (mSelectHandleRight == null) {
10451                mSelectHandleRight = mContext.getResources().getDrawable(
10452                        mTextSelectHandleRightRes);
10453            }
10454            mDrawable = mSelectHandleRight;
10455            mHotspotX = mDrawable.getIntrinsicWidth() / 4;
10456        }
10457
10458        @Override
10459        public int getCurrentCursorOffset() {
10460            return TextView.this.getSelectionEnd();
10461        }
10462
10463        @Override
10464        public void updateSelection(int offset) {
10465            Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
10466        }
10467
10468        @Override
10469        public void updatePosition(float x, float y) {
10470            final int selectionStart = getSelectionStart();
10471            final int selectionEnd = getSelectionEnd();
10472
10473            int offset = getOffsetForPosition(x, y);
10474
10475            // No need to redraw when the offset is unchanged
10476            if (offset == selectionEnd) return;
10477            // Handles can not cross and selection is at least one character
10478            if (offset <= selectionStart) offset = selectionStart + 1;
10479
10480            positionAtCursorOffset(offset);
10481        }
10482
10483        public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
10484            mActionPopupWindow = actionPopupWindow;
10485        }
10486    }
10487
10488    /**
10489     * A CursorController instance can be used to control a cursor in the text.
10490     * It is not used outside of {@link TextView}.
10491     * @hide
10492     */
10493    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
10494        /**
10495         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
10496         * See also {@link #hide()}.
10497         */
10498        public void show();
10499
10500        /**
10501         * Hide the cursor controller from screen.
10502         * See also {@link #show()}.
10503         */
10504        public void hide();
10505
10506        /**
10507         * Called when the view is detached from window. Perform house keeping task, such as
10508         * stopping Runnable thread that would otherwise keep a reference on the context, thus
10509         * preventing the activity from being recycled.
10510         */
10511        public void onDetached();
10512    }
10513
10514    private class InsertionPointCursorController implements CursorController {
10515        private InsertionHandleView mHandle;
10516
10517        public void show() {
10518            getHandle().show();
10519        }
10520
10521        public void showWithActionPopup() {
10522            getHandle().showWithActionPopup();
10523        }
10524
10525        public void hide() {
10526            if (mHandle != null) {
10527                mHandle.hide();
10528            }
10529        }
10530
10531        public void onTouchModeChanged(boolean isInTouchMode) {
10532            if (!isInTouchMode) {
10533                hide();
10534            }
10535        }
10536
10537        private InsertionHandleView getHandle() {
10538            if (mHandle == null) {
10539                mHandle = new InsertionHandleView();
10540            }
10541            return mHandle;
10542        }
10543
10544        @Override
10545        public void onDetached() {
10546            final ViewTreeObserver observer = getViewTreeObserver();
10547            observer.removeOnTouchModeChangeListener(this);
10548
10549            if (mHandle != null) mHandle.onDetached();
10550        }
10551    }
10552
10553    private class SelectionModifierCursorController implements CursorController {
10554        private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds
10555        // The cursor controller handles, lazily created when shown.
10556        private SelectionStartHandleView mStartHandle;
10557        private SelectionEndHandleView mEndHandle;
10558        // The offsets of that last touch down event. Remembered to start selection there.
10559        private int mMinTouchOffset, mMaxTouchOffset;
10560
10561        // Double tap detection
10562        private long mPreviousTapUpTime = 0;
10563        private float mPreviousTapPositionX, mPreviousTapPositionY;
10564
10565        SelectionModifierCursorController() {
10566            resetTouchOffsets();
10567        }
10568
10569        public void show() {
10570            if (isInBatchEditMode()) {
10571                return;
10572            }
10573
10574            // Lazy object creation has to be done before updatePosition() is called.
10575            if (mStartHandle == null) mStartHandle = new SelectionStartHandleView();
10576            if (mEndHandle == null) mEndHandle = new SelectionEndHandleView();
10577
10578            mStartHandle.show();
10579            mEndHandle.show();
10580
10581            // Make sure both left and right handles share the same ActionPopupWindow (so that
10582            // moving any of the handles hides the action popup).
10583            mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION);
10584            mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
10585
10586            hideInsertionPointCursorController();
10587        }
10588
10589        public void hide() {
10590            if (mStartHandle != null) mStartHandle.hide();
10591            if (mEndHandle != null) mEndHandle.hide();
10592        }
10593
10594        public void onTouchEvent(MotionEvent event) {
10595            // This is done even when the View does not have focus, so that long presses can start
10596            // selection and tap can move cursor from this tap position.
10597            switch (event.getActionMasked()) {
10598                case MotionEvent.ACTION_DOWN:
10599                    final float x = event.getX();
10600                    final float y = event.getY();
10601
10602                    // Remember finger down position, to be able to start selection from there
10603                    mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
10604
10605                    // Double tap detection
10606                    long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
10607                    if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
10608                            isPositionOnText(x, y)) {
10609                        final float deltaX = x - mPreviousTapPositionX;
10610                        final float deltaY = y - mPreviousTapPositionY;
10611                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
10612                        if (distanceSquared < mSquaredTouchSlopDistance) {
10613                            startSelectionActionMode();
10614                            mDiscardNextActionUp = true;
10615                        }
10616                    }
10617
10618                    mPreviousTapPositionX = x;
10619                    mPreviousTapPositionY = y;
10620                    break;
10621
10622                case MotionEvent.ACTION_POINTER_DOWN:
10623                case MotionEvent.ACTION_POINTER_UP:
10624                    // Handle multi-point gestures. Keep min and max offset positions.
10625                    // Only activated for devices that correctly handle multi-touch.
10626                    if (mContext.getPackageManager().hasSystemFeature(
10627                            PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
10628                        updateMinAndMaxOffsets(event);
10629                    }
10630                    break;
10631
10632                case MotionEvent.ACTION_UP:
10633                    mPreviousTapUpTime = SystemClock.uptimeMillis();
10634                    break;
10635            }
10636        }
10637
10638        /**
10639         * @param event
10640         */
10641        private void updateMinAndMaxOffsets(MotionEvent event) {
10642            int pointerCount = event.getPointerCount();
10643            for (int index = 0; index < pointerCount; index++) {
10644                int offset = getOffsetForPosition(event.getX(index), event.getY(index));
10645                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
10646                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
10647            }
10648        }
10649
10650        public int getMinTouchOffset() {
10651            return mMinTouchOffset;
10652        }
10653
10654        public int getMaxTouchOffset() {
10655            return mMaxTouchOffset;
10656        }
10657
10658        public void resetTouchOffsets() {
10659            mMinTouchOffset = mMaxTouchOffset = -1;
10660        }
10661
10662        /**
10663         * @return true iff this controller is currently used to move the selection start.
10664         */
10665        public boolean isSelectionStartDragged() {
10666            return mStartHandle != null && mStartHandle.isDragging();
10667        }
10668
10669        public void onTouchModeChanged(boolean isInTouchMode) {
10670            if (!isInTouchMode) {
10671                hide();
10672            }
10673        }
10674
10675        @Override
10676        public void onDetached() {
10677            final ViewTreeObserver observer = getViewTreeObserver();
10678            observer.removeOnTouchModeChangeListener(this);
10679
10680            if (mStartHandle != null) mStartHandle.onDetached();
10681            if (mEndHandle != null) mEndHandle.onDetached();
10682        }
10683    }
10684
10685    private void hideInsertionPointCursorController() {
10686        // No need to create the controller to hide it.
10687        if (mInsertionPointCursorController != null) {
10688            mInsertionPointCursorController.hide();
10689        }
10690    }
10691
10692    /**
10693     * Hides the insertion controller and stops text selection mode, hiding the selection controller
10694     */
10695    private void hideControllers() {
10696        hideInsertionPointCursorController();
10697        stopSelectionActionMode();
10698    }
10699
10700    /**
10701     * Get the character offset closest to the specified absolute position. A typical use case is to
10702     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
10703     *
10704     * @param x The horizontal absolute position of a point on screen
10705     * @param y The vertical absolute position of a point on screen
10706     * @return the character offset for the character whose position is closest to the specified
10707     *  position. Returns -1 if there is no layout.
10708     */
10709    public int getOffsetForPosition(float x, float y) {
10710        if (getLayout() == null) return -1;
10711        final int line = getLineAtCoordinate(y);
10712        final int offset = getOffsetAtCoordinate(line, x);
10713        return offset;
10714    }
10715
10716    private float convertToLocalHorizontalCoordinate(float x) {
10717        x -= getTotalPaddingLeft();
10718        // Clamp the position to inside of the view.
10719        x = Math.max(0.0f, x);
10720        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
10721        x += getScrollX();
10722        return x;
10723    }
10724
10725    private int getLineAtCoordinate(float y) {
10726        y -= getTotalPaddingTop();
10727        // Clamp the position to inside of the view.
10728        y = Math.max(0.0f, y);
10729        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
10730        y += getScrollY();
10731        return getLayout().getLineForVertical((int) y);
10732    }
10733
10734    private int getOffsetAtCoordinate(int line, float x) {
10735        x = convertToLocalHorizontalCoordinate(x);
10736        return getLayout().getOffsetForHorizontal(line, x);
10737    }
10738
10739    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
10740     * in the view. Returns false when the position is in the empty space of left/right of text.
10741     */
10742    private boolean isPositionOnText(float x, float y) {
10743        if (getLayout() == null) return false;
10744
10745        final int line = getLineAtCoordinate(y);
10746        x = convertToLocalHorizontalCoordinate(x);
10747
10748        if (x < getLayout().getLineLeft(line)) return false;
10749        if (x > getLayout().getLineRight(line)) return false;
10750        return true;
10751    }
10752
10753    @Override
10754    public boolean onDragEvent(DragEvent event) {
10755        switch (event.getAction()) {
10756            case DragEvent.ACTION_DRAG_STARTED:
10757                return hasInsertionController();
10758
10759            case DragEvent.ACTION_DRAG_ENTERED:
10760                TextView.this.requestFocus();
10761                return true;
10762
10763            case DragEvent.ACTION_DRAG_LOCATION:
10764                final int offset = getOffsetForPosition(event.getX(), event.getY());
10765                Selection.setSelection((Spannable)mText, offset);
10766                return true;
10767
10768            case DragEvent.ACTION_DROP:
10769                onDrop(event);
10770                return true;
10771
10772            case DragEvent.ACTION_DRAG_ENDED:
10773            case DragEvent.ACTION_DRAG_EXITED:
10774            default:
10775                return true;
10776        }
10777    }
10778
10779    private void onDrop(DragEvent event) {
10780        StringBuilder content = new StringBuilder("");
10781        ClipData clipData = event.getClipData();
10782        final int itemCount = clipData.getItemCount();
10783        for (int i=0; i < itemCount; i++) {
10784            Item item = clipData.getItemAt(i);
10785            content.append(item.coerceToText(TextView.this.mContext));
10786        }
10787
10788        final int offset = getOffsetForPosition(event.getX(), event.getY());
10789
10790        Object localState = event.getLocalState();
10791        DragLocalState dragLocalState = null;
10792        if (localState instanceof DragLocalState) {
10793            dragLocalState = (DragLocalState) localState;
10794        }
10795        boolean dragDropIntoItself = dragLocalState != null &&
10796                dragLocalState.sourceTextView == this;
10797
10798        if (dragDropIntoItself) {
10799            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
10800                // A drop inside the original selection discards the drop.
10801                return;
10802            }
10803        }
10804
10805        final int originalLength = mText.length();
10806        long minMax = prepareSpacesAroundPaste(offset, offset, content);
10807        int min = extractRangeStartFromLong(minMax);
10808        int max = extractRangeEndFromLong(minMax);
10809
10810        Selection.setSelection((Spannable) mText, max);
10811        ((Editable) mText).replace(min, max, content);
10812
10813        if (dragDropIntoItself) {
10814            int dragSourceStart = dragLocalState.start;
10815            int dragSourceEnd = dragLocalState.end;
10816            if (max <= dragSourceStart) {
10817                // Inserting text before selection has shifted positions
10818                final int shift = mText.length() - originalLength;
10819                dragSourceStart += shift;
10820                dragSourceEnd += shift;
10821            }
10822
10823            // Delete original selection
10824            ((Editable) mText).delete(dragSourceStart, dragSourceEnd);
10825
10826            // Make sure we do not leave two adjacent spaces.
10827            if ((dragSourceStart == 0 ||
10828                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
10829                    (dragSourceStart == mText.length() ||
10830                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
10831                final int pos = dragSourceStart == mText.length() ?
10832                        dragSourceStart - 1 : dragSourceStart;
10833                ((Editable) mText).delete(pos, pos + 1);
10834            }
10835        }
10836    }
10837
10838    /**
10839     * @return True if this view supports insertion handles.
10840     */
10841    boolean hasInsertionController() {
10842        return mInsertionControllerEnabled;
10843    }
10844
10845    /**
10846     * @return True if this view supports selection handles.
10847     */
10848    boolean hasSelectionController() {
10849        return mSelectionControllerEnabled;
10850    }
10851
10852    InsertionPointCursorController getInsertionController() {
10853        if (!mInsertionControllerEnabled) {
10854            return null;
10855        }
10856
10857        if (mInsertionPointCursorController == null) {
10858            mInsertionPointCursorController = new InsertionPointCursorController();
10859
10860            final ViewTreeObserver observer = getViewTreeObserver();
10861            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
10862        }
10863
10864        return mInsertionPointCursorController;
10865    }
10866
10867    SelectionModifierCursorController getSelectionController() {
10868        if (!mSelectionControllerEnabled) {
10869            return null;
10870        }
10871
10872        if (mSelectionModifierCursorController == null) {
10873            mSelectionModifierCursorController = new SelectionModifierCursorController();
10874
10875            final ViewTreeObserver observer = getViewTreeObserver();
10876            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
10877        }
10878
10879        return mSelectionModifierCursorController;
10880    }
10881
10882    boolean isInBatchEditMode() {
10883        final InputMethodState ims = mInputMethodState;
10884        if (ims != null) {
10885            return ims.mBatchEditNesting > 0;
10886        }
10887        return mInBatchEditControllers;
10888    }
10889
10890    @Override
10891    protected void resolveTextDirection() {
10892        if (hasPasswordTransformationMethod()) {
10893            mTextDir = TextDirectionHeuristics.LOCALE;
10894            return;
10895        }
10896
10897        // Always need to resolve layout direction first
10898        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
10899
10900        // Then resolve text direction on the parent
10901        super.resolveTextDirection();
10902
10903        // Now, we can select the heuristic
10904        int textDir = getResolvedTextDirection();
10905        switch (textDir) {
10906            default:
10907            case TEXT_DIRECTION_FIRST_STRONG:
10908                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
10909                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
10910                break;
10911            case TEXT_DIRECTION_ANY_RTL:
10912                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
10913                break;
10914            case TEXT_DIRECTION_CHAR_COUNT:
10915                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.CHARCOUNT_RTL:
10916                        TextDirectionHeuristics.CHARCOUNT_LTR);
10917                break;
10918            case TEXT_DIRECTION_LTR:
10919                mTextDir = TextDirectionHeuristics.LTR;
10920                break;
10921            case TEXT_DIRECTION_RTL:
10922                mTextDir = TextDirectionHeuristics.RTL;
10923                break;
10924        }
10925    }
10926
10927    /**
10928     * Subclasses will need to override this method to implement their own way of resolving
10929     * drawables depending on the layout direction.
10930     *
10931     * A call to the super method will be required from the subclasses implementation.
10932     *
10933     */
10934    protected void resolveDrawables() {
10935        // No need to resolve twice
10936        if (mResolvedDrawables) {
10937            return;
10938        }
10939        // No drawable to resolve
10940        if (mDrawables == null) {
10941            return;
10942        }
10943        // No relative drawable to resolve
10944        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
10945            mResolvedDrawables = true;
10946            return;
10947        }
10948
10949        Drawables dr = mDrawables;
10950        switch(getResolvedLayoutDirection()) {
10951            case LAYOUT_DIRECTION_RTL:
10952                if (dr.mDrawableStart != null) {
10953                    dr.mDrawableRight = dr.mDrawableStart;
10954
10955                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
10956                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
10957                }
10958                if (dr.mDrawableEnd != null) {
10959                    dr.mDrawableLeft = dr.mDrawableEnd;
10960
10961                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
10962                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
10963                }
10964                break;
10965
10966            case LAYOUT_DIRECTION_LTR:
10967            default:
10968                if (dr.mDrawableStart != null) {
10969                    dr.mDrawableLeft = dr.mDrawableStart;
10970
10971                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
10972                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
10973                }
10974                if (dr.mDrawableEnd != null) {
10975                    dr.mDrawableRight = dr.mDrawableEnd;
10976
10977                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
10978                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
10979                }
10980                break;
10981        }
10982        mResolvedDrawables = true;
10983    }
10984
10985    protected void resetResolvedDrawables() {
10986        mResolvedDrawables = false;
10987    }
10988
10989    @ViewDebug.ExportedProperty(category = "text")
10990    private CharSequence            mText;
10991    private CharSequence            mTransformed;
10992    private BufferType              mBufferType = BufferType.NORMAL;
10993
10994    private int                     mInputType = EditorInfo.TYPE_NULL;
10995    private CharSequence            mHint;
10996    private Layout                  mHintLayout;
10997
10998    private KeyListener             mInput;
10999
11000    private MovementMethod          mMovement;
11001    private TransformationMethod    mTransformation;
11002    private boolean                 mAllowTransformationLengthChange;
11003    private ChangeWatcher           mChangeWatcher;
11004
11005    private ArrayList<TextWatcher>  mListeners = null;
11006
11007    // display attributes
11008    private final TextPaint         mTextPaint;
11009    private boolean                 mUserSetTextScaleX;
11010    private final Paint             mHighlightPaint;
11011    private int                     mHighlightColor = 0xCC475925;
11012    /**
11013     * This is temporarily visible to fix bug 3085564 in webView. Do not rely on
11014     * this field being protected. Will be restored as private when lineHeight
11015     * feature request 3215097 is implemented
11016     * @hide
11017     */
11018    protected Layout                mLayout;
11019
11020    private long                    mShowCursor;
11021    private Blink                   mBlink;
11022    private boolean                 mCursorVisible = true;
11023
11024    // Cursor Controllers.
11025    private InsertionPointCursorController mInsertionPointCursorController;
11026    private SelectionModifierCursorController mSelectionModifierCursorController;
11027    private ActionMode              mSelectionActionMode;
11028    private boolean                 mInsertionControllerEnabled;
11029    private boolean                 mSelectionControllerEnabled;
11030    private boolean                 mInBatchEditControllers;
11031
11032    // These are needed to desambiguate a long click. If the long click comes from ones of these, we
11033    // select from the current cursor position. Otherwise, select from long pressed position.
11034    private boolean                 mDPadCenterIsDown = false;
11035    private boolean                 mEnterKeyIsDown = false;
11036    private boolean                 mContextMenuTriggeredByKey = false;
11037
11038    private boolean                 mSelectAllOnFocus = false;
11039
11040    private int                     mGravity = Gravity.TOP | Gravity.START;
11041    private boolean                 mHorizontallyScrolling;
11042
11043    private int                     mAutoLinkMask;
11044    private boolean                 mLinksClickable = true;
11045
11046    private float                   mSpacingMult = 1.0f;
11047    private float                   mSpacingAdd = 0.0f;
11048    private boolean                 mTextIsSelectable = false;
11049
11050    private static final int        LINES = 1;
11051    private static final int        EMS = LINES;
11052    private static final int        PIXELS = 2;
11053
11054    private int                     mMaximum = Integer.MAX_VALUE;
11055    private int                     mMaxMode = LINES;
11056    private int                     mMinimum = 0;
11057    private int                     mMinMode = LINES;
11058
11059    private int                     mOldMaximum = mMaximum;
11060    private int                     mOldMaxMode = mMaxMode;
11061
11062    private int                     mMaxWidth = Integer.MAX_VALUE;
11063    private int                     mMaxWidthMode = PIXELS;
11064    private int                     mMinWidth = 0;
11065    private int                     mMinWidthMode = PIXELS;
11066
11067    private boolean                 mSingleLine;
11068    private int                     mDesiredHeightAtMeasure = -1;
11069    private boolean                 mIncludePad = true;
11070
11071    // tmp primitives, so we don't alloc them on each draw
11072    private Path                    mHighlightPath;
11073    private boolean                 mHighlightPathBogus = true;
11074    private static final RectF      sTempRect = new RectF();
11075
11076    // XXX should be much larger
11077    private static final int        VERY_WIDE = 16384;
11078
11079    private static final int        BLINK = 500;
11080
11081    private static final int ANIMATED_SCROLL_GAP = 250;
11082    private long mLastScroll;
11083    private Scroller mScroller = null;
11084
11085    private BoringLayout.Metrics mBoring;
11086    private BoringLayout.Metrics mHintBoring;
11087
11088    private BoringLayout mSavedLayout, mSavedHintLayout;
11089
11090    private TextDirectionHeuristic mTextDir = null;
11091
11092    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
11093    private InputFilter[] mFilters = NO_FILTERS;
11094    private static final Spanned EMPTY_SPANNED = new SpannedString("");
11095    private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
11096    // System wide time for last cut or copy action.
11097    private static long sLastCutOrCopyTime;
11098    // Used to highlight a word when it is corrected by the IME
11099    private CorrectionHighlighter mCorrectionHighlighter;
11100    // New state used to change background based on whether this TextView is multiline.
11101    private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
11102}
11103