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