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