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