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