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