TextView.java revision ad8da91895afa7e8a97a9d681471d51924461cff
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.content.Context;
25import android.content.Intent;
26import android.content.res.ColorStateList;
27import android.content.res.Resources;
28import android.content.res.TypedArray;
29import android.content.res.XmlResourceParser;
30import android.graphics.Canvas;
31import android.graphics.Paint;
32import android.graphics.Path;
33import android.graphics.Rect;
34import android.graphics.RectF;
35import android.graphics.Typeface;
36import android.graphics.drawable.Drawable;
37import android.os.Bundle;
38import android.os.Handler;
39import android.os.Message;
40import android.os.Parcel;
41import android.os.Parcelable;
42import android.os.ResultReceiver;
43import android.os.SystemClock;
44import android.text.BoringLayout;
45import android.text.ClipboardManager;
46import android.text.DynamicLayout;
47import android.text.Editable;
48import android.text.GetChars;
49import android.text.GraphicsOperations;
50import android.text.InputFilter;
51import android.text.InputType;
52import android.text.Layout;
53import android.text.ParcelableSpan;
54import android.text.Selection;
55import android.text.SpanWatcher;
56import android.text.Spannable;
57import android.text.SpannableString;
58import android.text.Spanned;
59import android.text.SpannedString;
60import android.text.StaticLayout;
61import android.text.TextPaint;
62import android.text.TextUtils;
63import android.text.TextWatcher;
64import android.text.method.DateKeyListener;
65import android.text.method.DateTimeKeyListener;
66import android.text.method.DialerKeyListener;
67import android.text.method.DigitsKeyListener;
68import android.text.method.KeyListener;
69import android.text.method.LinkMovementMethod;
70import android.text.method.MetaKeyKeyListener;
71import android.text.method.MovementMethod;
72import android.text.method.PasswordTransformationMethod;
73import android.text.method.SingleLineTransformationMethod;
74import android.text.method.TextKeyListener;
75import android.text.method.TimeKeyListener;
76import android.text.method.TransformationMethod;
77import android.text.style.ParagraphStyle;
78import android.text.style.URLSpan;
79import android.text.style.UpdateAppearance;
80import android.text.util.Linkify;
81import android.util.AttributeSet;
82import android.util.FloatMath;
83import android.util.Log;
84import android.util.TypedValue;
85import android.view.ContextMenu;
86import android.view.Gravity;
87import android.view.KeyEvent;
88import android.view.LayoutInflater;
89import android.view.MenuItem;
90import android.view.MotionEvent;
91import android.view.View;
92import android.view.ViewDebug;
93import android.view.ViewRoot;
94import android.view.ViewTreeObserver;
95import android.view.ViewGroup.LayoutParams;
96import android.view.accessibility.AccessibilityEvent;
97import android.view.accessibility.AccessibilityManager;
98import android.view.animation.AnimationUtils;
99import android.view.inputmethod.BaseInputConnection;
100import android.view.inputmethod.CompletionInfo;
101import android.view.inputmethod.EditorInfo;
102import android.view.inputmethod.ExtractedText;
103import android.view.inputmethod.ExtractedTextRequest;
104import android.view.inputmethod.InputConnection;
105import android.view.inputmethod.InputMethodManager;
106import android.widget.RemoteViews.RemoteView;
107
108import java.io.IOException;
109import java.lang.ref.WeakReference;
110import java.util.ArrayList;
111
112/**
113 * Displays text to the user and optionally allows them to edit it.  A TextView
114 * is a complete text editor, however the basic class is configured to not
115 * allow editing; see {@link EditText} for a subclass that configures the text
116 * view for editing.
117 *
118 * <p>
119 * <b>XML attributes</b>
120 * <p>
121 * See {@link android.R.styleable#TextView TextView Attributes},
122 * {@link android.R.styleable#View View Attributes}
123 *
124 * @attr ref android.R.styleable#TextView_text
125 * @attr ref android.R.styleable#TextView_bufferType
126 * @attr ref android.R.styleable#TextView_hint
127 * @attr ref android.R.styleable#TextView_textColor
128 * @attr ref android.R.styleable#TextView_textColorHighlight
129 * @attr ref android.R.styleable#TextView_textColorHint
130 * @attr ref android.R.styleable#TextView_textAppearance
131 * @attr ref android.R.styleable#TextView_textColorLink
132 * @attr ref android.R.styleable#TextView_textSize
133 * @attr ref android.R.styleable#TextView_textScaleX
134 * @attr ref android.R.styleable#TextView_typeface
135 * @attr ref android.R.styleable#TextView_textStyle
136 * @attr ref android.R.styleable#TextView_cursorVisible
137 * @attr ref android.R.styleable#TextView_maxLines
138 * @attr ref android.R.styleable#TextView_maxHeight
139 * @attr ref android.R.styleable#TextView_lines
140 * @attr ref android.R.styleable#TextView_height
141 * @attr ref android.R.styleable#TextView_minLines
142 * @attr ref android.R.styleable#TextView_minHeight
143 * @attr ref android.R.styleable#TextView_maxEms
144 * @attr ref android.R.styleable#TextView_maxWidth
145 * @attr ref android.R.styleable#TextView_ems
146 * @attr ref android.R.styleable#TextView_width
147 * @attr ref android.R.styleable#TextView_minEms
148 * @attr ref android.R.styleable#TextView_minWidth
149 * @attr ref android.R.styleable#TextView_gravity
150 * @attr ref android.R.styleable#TextView_scrollHorizontally
151 * @attr ref android.R.styleable#TextView_password
152 * @attr ref android.R.styleable#TextView_singleLine
153 * @attr ref android.R.styleable#TextView_selectAllOnFocus
154 * @attr ref android.R.styleable#TextView_includeFontPadding
155 * @attr ref android.R.styleable#TextView_maxLength
156 * @attr ref android.R.styleable#TextView_shadowColor
157 * @attr ref android.R.styleable#TextView_shadowDx
158 * @attr ref android.R.styleable#TextView_shadowDy
159 * @attr ref android.R.styleable#TextView_shadowRadius
160 * @attr ref android.R.styleable#TextView_autoLink
161 * @attr ref android.R.styleable#TextView_linksClickable
162 * @attr ref android.R.styleable#TextView_numeric
163 * @attr ref android.R.styleable#TextView_digits
164 * @attr ref android.R.styleable#TextView_phoneNumber
165 * @attr ref android.R.styleable#TextView_inputMethod
166 * @attr ref android.R.styleable#TextView_capitalize
167 * @attr ref android.R.styleable#TextView_autoText
168 * @attr ref android.R.styleable#TextView_editable
169 * @attr ref android.R.styleable#TextView_freezesText
170 * @attr ref android.R.styleable#TextView_ellipsize
171 * @attr ref android.R.styleable#TextView_drawableTop
172 * @attr ref android.R.styleable#TextView_drawableBottom
173 * @attr ref android.R.styleable#TextView_drawableRight
174 * @attr ref android.R.styleable#TextView_drawableLeft
175 * @attr ref android.R.styleable#TextView_drawablePadding
176 * @attr ref android.R.styleable#TextView_lineSpacingExtra
177 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
178 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
179 * @attr ref android.R.styleable#TextView_inputType
180 * @attr ref android.R.styleable#TextView_imeOptions
181 * @attr ref android.R.styleable#TextView_privateImeOptions
182 * @attr ref android.R.styleable#TextView_imeActionLabel
183 * @attr ref android.R.styleable#TextView_imeActionId
184 * @attr ref android.R.styleable#TextView_editorExtras
185 */
186@RemoteView
187public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
188    static final String TAG = "TextView";
189    static final boolean DEBUG_EXTRACT = false;
190
191    private static int PRIORITY = 100;
192
193    private ColorStateList mTextColor;
194    private int mCurTextColor;
195    private ColorStateList mHintTextColor;
196    private ColorStateList mLinkTextColor;
197    private int mCurHintTextColor;
198    private boolean mFreezesText;
199    private boolean mFrozenWithFocus;
200    private boolean mTemporaryDetach;
201
202    private boolean mEatTouchRelease = false;
203    private boolean mScrolled = false;
204
205    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
206    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
207
208    private float mShadowRadius, mShadowDx, mShadowDy;
209
210    private static final int PREDRAW_NOT_REGISTERED = 0;
211    private static final int PREDRAW_PENDING = 1;
212    private static final int PREDRAW_DONE = 2;
213    private int mPreDrawState = PREDRAW_NOT_REGISTERED;
214
215    private TextUtils.TruncateAt mEllipsize = null;
216
217    // Enum for the "typeface" XML parameter.
218    // TODO: How can we get this from the XML instead of hardcoding it here?
219    private static final int SANS = 1;
220    private static final int SERIF = 2;
221    private static final int MONOSPACE = 3;
222
223    // Bitfield for the "numeric" XML parameter.
224    // TODO: How can we get this from the XML instead of hardcoding it here?
225    private static final int SIGNED = 2;
226    private static final int DECIMAL = 4;
227
228    class Drawables {
229        final Rect mCompoundRect = new Rect();
230        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
231        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
232        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
233        int mDrawablePadding;
234    }
235    private Drawables mDrawables;
236
237    private CharSequence mError;
238    private boolean mErrorWasChanged;
239    private ErrorPopup mPopup;
240    /**
241     * This flag is set if the TextView tries to display an error before it
242     * is attached to the window (so its position is still unknown).
243     * It causes the error to be shown later, when onAttachedToWindow()
244     * is called.
245     */
246    private boolean mShowErrorAfterAttach;
247
248    private CharWrapper mCharWrapper = null;
249
250    private boolean mSelectionMoved = false;
251    private boolean mTouchFocusSelected = false;
252
253    private Marquee mMarquee;
254    private boolean mRestartMarquee;
255
256    private int mMarqueeRepeatLimit = 3;
257
258    class InputContentType {
259        int imeOptions = EditorInfo.IME_NULL;
260        String privateImeOptions;
261        CharSequence imeActionLabel;
262        int imeActionId;
263        Bundle extras;
264        OnEditorActionListener onEditorActionListener;
265        boolean enterDown;
266    }
267    InputContentType mInputContentType;
268
269    class InputMethodState {
270        Rect mCursorRectInWindow = new Rect();
271        RectF mTmpRectF = new RectF();
272        float[] mTmpOffset = new float[2];
273        ExtractedTextRequest mExtracting;
274        final ExtractedText mTmpExtracted = new ExtractedText();
275        int mBatchEditNesting;
276        boolean mCursorChanged;
277        boolean mSelectionModeChanged;
278        boolean mContentChanged;
279        int mChangedStart, mChangedEnd, mChangedDelta;
280    }
281    InputMethodState mInputMethodState;
282
283    /*
284     * Kick-start the font cache for the zygote process (to pay the cost of
285     * initializing freetype for our default font only once).
286     */
287    static {
288        Paint p = new Paint();
289        p.setAntiAlias(true);
290        // We don't care about the result, just the side-effect of measuring.
291        p.measureText("H");
292    }
293
294    /**
295     * Interface definition for a callback to be invoked when an action is
296     * performed on the editor.
297     */
298    public interface OnEditorActionListener {
299        /**
300         * Called when an action is being performed.
301         *
302         * @param v The view that was clicked.
303         * @param actionId Identifier of the action.  This will be either the
304         * identifier you supplied, or {@link EditorInfo#IME_NULL
305         * EditorInfo.IME_NULL} if being called due to the enter key
306         * being pressed.
307         * @param event If triggered by an enter key, this is the event;
308         * otherwise, this is null.
309         * @return Return true if you have consumed the action, else false.
310         */
311        boolean onEditorAction(TextView v, int actionId, KeyEvent event);
312    }
313
314    public TextView(Context context) {
315        this(context, null);
316    }
317
318    public TextView(Context context,
319                    AttributeSet attrs) {
320        this(context, attrs, com.android.internal.R.attr.textViewStyle);
321    }
322
323    public TextView(Context context,
324                    AttributeSet attrs,
325                    int defStyle) {
326        super(context, attrs, defStyle);
327        mText = "";
328
329        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
330        mTextPaint.density = getResources().getDisplayMetrics().density;
331        // If we get the paint from the skin, we should set it to left, since
332        // the layout always wants it to be left.
333        // mTextPaint.setTextAlign(Paint.Align.LEFT);
334
335        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
336
337        mMovement = getDefaultMovementMethod();
338        mTransformation = null;
339
340        TypedArray a =
341            context.obtainStyledAttributes(
342                attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
343
344        int textColorHighlight = 0;
345        ColorStateList textColor = null;
346        ColorStateList textColorHint = null;
347        ColorStateList textColorLink = null;
348        int textSize = 15;
349        int typefaceIndex = -1;
350        int styleIndex = -1;
351
352        /*
353         * Look the appearance up without checking first if it exists because
354         * almost every TextView has one and it greatly simplifies the logic
355         * to be able to parse the appearance first and then let specific tags
356         * for this View override it.
357         */
358        TypedArray appearance = null;
359        int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
360        if (ap != -1) {
361            appearance = context.obtainStyledAttributes(ap,
362                                com.android.internal.R.styleable.
363                                TextAppearance);
364        }
365        if (appearance != null) {
366            int n = appearance.getIndexCount();
367            for (int i = 0; i < n; i++) {
368                int attr = appearance.getIndex(i);
369
370                switch (attr) {
371                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
372                    textColorHighlight = appearance.getColor(attr, textColorHighlight);
373                    break;
374
375                case com.android.internal.R.styleable.TextAppearance_textColor:
376                    textColor = appearance.getColorStateList(attr);
377                    break;
378
379                case com.android.internal.R.styleable.TextAppearance_textColorHint:
380                    textColorHint = appearance.getColorStateList(attr);
381                    break;
382
383                case com.android.internal.R.styleable.TextAppearance_textColorLink:
384                    textColorLink = appearance.getColorStateList(attr);
385                    break;
386
387                case com.android.internal.R.styleable.TextAppearance_textSize:
388                    textSize = appearance.getDimensionPixelSize(attr, textSize);
389                    break;
390
391                case com.android.internal.R.styleable.TextAppearance_typeface:
392                    typefaceIndex = appearance.getInt(attr, -1);
393                    break;
394
395                case com.android.internal.R.styleable.TextAppearance_textStyle:
396                    styleIndex = appearance.getInt(attr, -1);
397                    break;
398                }
399            }
400
401            appearance.recycle();
402        }
403
404        boolean editable = getDefaultEditable();
405        CharSequence inputMethod = null;
406        int numeric = 0;
407        CharSequence digits = null;
408        boolean phone = false;
409        boolean autotext = false;
410        int autocap = -1;
411        int buffertype = 0;
412        boolean selectallonfocus = false;
413        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
414            drawableBottom = null;
415        int drawablePadding = 0;
416        int ellipsize = -1;
417        boolean singleLine = false;
418        int maxlength = -1;
419        CharSequence text = "";
420        CharSequence hint = null;
421        int shadowcolor = 0;
422        float dx = 0, dy = 0, r = 0;
423        boolean password = false;
424        int inputType = EditorInfo.TYPE_NULL;
425
426        int n = a.getIndexCount();
427        for (int i = 0; i < n; i++) {
428            int attr = a.getIndex(i);
429
430            switch (attr) {
431            case com.android.internal.R.styleable.TextView_editable:
432                editable = a.getBoolean(attr, editable);
433                break;
434
435            case com.android.internal.R.styleable.TextView_inputMethod:
436                inputMethod = a.getText(attr);
437                break;
438
439            case com.android.internal.R.styleable.TextView_numeric:
440                numeric = a.getInt(attr, numeric);
441                break;
442
443            case com.android.internal.R.styleable.TextView_digits:
444                digits = a.getText(attr);
445                break;
446
447            case com.android.internal.R.styleable.TextView_phoneNumber:
448                phone = a.getBoolean(attr, phone);
449                break;
450
451            case com.android.internal.R.styleable.TextView_autoText:
452                autotext = a.getBoolean(attr, autotext);
453                break;
454
455            case com.android.internal.R.styleable.TextView_capitalize:
456                autocap = a.getInt(attr, autocap);
457                break;
458
459            case com.android.internal.R.styleable.TextView_bufferType:
460                buffertype = a.getInt(attr, buffertype);
461                break;
462
463            case com.android.internal.R.styleable.TextView_selectAllOnFocus:
464                selectallonfocus = a.getBoolean(attr, selectallonfocus);
465                break;
466
467            case com.android.internal.R.styleable.TextView_autoLink:
468                mAutoLinkMask = a.getInt(attr, 0);
469                break;
470
471            case com.android.internal.R.styleable.TextView_linksClickable:
472                mLinksClickable = a.getBoolean(attr, true);
473                break;
474
475            case com.android.internal.R.styleable.TextView_drawableLeft:
476                drawableLeft = a.getDrawable(attr);
477                break;
478
479            case com.android.internal.R.styleable.TextView_drawableTop:
480                drawableTop = a.getDrawable(attr);
481                break;
482
483            case com.android.internal.R.styleable.TextView_drawableRight:
484                drawableRight = a.getDrawable(attr);
485                break;
486
487            case com.android.internal.R.styleable.TextView_drawableBottom:
488                drawableBottom = a.getDrawable(attr);
489                break;
490
491            case com.android.internal.R.styleable.TextView_drawablePadding:
492                drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
493                break;
494
495            case com.android.internal.R.styleable.TextView_maxLines:
496                setMaxLines(a.getInt(attr, -1));
497                break;
498
499            case com.android.internal.R.styleable.TextView_maxHeight:
500                setMaxHeight(a.getDimensionPixelSize(attr, -1));
501                break;
502
503            case com.android.internal.R.styleable.TextView_lines:
504                setLines(a.getInt(attr, -1));
505                break;
506
507            case com.android.internal.R.styleable.TextView_height:
508                setHeight(a.getDimensionPixelSize(attr, -1));
509                break;
510
511            case com.android.internal.R.styleable.TextView_minLines:
512                setMinLines(a.getInt(attr, -1));
513                break;
514
515            case com.android.internal.R.styleable.TextView_minHeight:
516                setMinHeight(a.getDimensionPixelSize(attr, -1));
517                break;
518
519            case com.android.internal.R.styleable.TextView_maxEms:
520                setMaxEms(a.getInt(attr, -1));
521                break;
522
523            case com.android.internal.R.styleable.TextView_maxWidth:
524                setMaxWidth(a.getDimensionPixelSize(attr, -1));
525                break;
526
527            case com.android.internal.R.styleable.TextView_ems:
528                setEms(a.getInt(attr, -1));
529                break;
530
531            case com.android.internal.R.styleable.TextView_width:
532                setWidth(a.getDimensionPixelSize(attr, -1));
533                break;
534
535            case com.android.internal.R.styleable.TextView_minEms:
536                setMinEms(a.getInt(attr, -1));
537                break;
538
539            case com.android.internal.R.styleable.TextView_minWidth:
540                setMinWidth(a.getDimensionPixelSize(attr, -1));
541                break;
542
543            case com.android.internal.R.styleable.TextView_gravity:
544                setGravity(a.getInt(attr, -1));
545                break;
546
547            case com.android.internal.R.styleable.TextView_hint:
548                hint = a.getText(attr);
549                break;
550
551            case com.android.internal.R.styleable.TextView_text:
552                text = a.getText(attr);
553                break;
554
555            case com.android.internal.R.styleable.TextView_scrollHorizontally:
556                if (a.getBoolean(attr, false)) {
557                    setHorizontallyScrolling(true);
558                }
559                break;
560
561            case com.android.internal.R.styleable.TextView_singleLine:
562                singleLine = a.getBoolean(attr, singleLine);
563                break;
564
565            case com.android.internal.R.styleable.TextView_ellipsize:
566                ellipsize = a.getInt(attr, ellipsize);
567                break;
568
569            case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
570                setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
571                break;
572
573            case com.android.internal.R.styleable.TextView_includeFontPadding:
574                if (!a.getBoolean(attr, true)) {
575                    setIncludeFontPadding(false);
576                }
577                break;
578
579            case com.android.internal.R.styleable.TextView_cursorVisible:
580                if (!a.getBoolean(attr, true)) {
581                    setCursorVisible(false);
582                }
583                break;
584
585            case com.android.internal.R.styleable.TextView_maxLength:
586                maxlength = a.getInt(attr, -1);
587                break;
588
589            case com.android.internal.R.styleable.TextView_textScaleX:
590                setTextScaleX(a.getFloat(attr, 1.0f));
591                break;
592
593            case com.android.internal.R.styleable.TextView_freezesText:
594                mFreezesText = a.getBoolean(attr, false);
595                break;
596
597            case com.android.internal.R.styleable.TextView_shadowColor:
598                shadowcolor = a.getInt(attr, 0);
599                break;
600
601            case com.android.internal.R.styleable.TextView_shadowDx:
602                dx = a.getFloat(attr, 0);
603                break;
604
605            case com.android.internal.R.styleable.TextView_shadowDy:
606                dy = a.getFloat(attr, 0);
607                break;
608
609            case com.android.internal.R.styleable.TextView_shadowRadius:
610                r = a.getFloat(attr, 0);
611                break;
612
613            case com.android.internal.R.styleable.TextView_enabled:
614                setEnabled(a.getBoolean(attr, isEnabled()));
615                break;
616
617            case com.android.internal.R.styleable.TextView_textColorHighlight:
618                textColorHighlight = a.getColor(attr, textColorHighlight);
619                break;
620
621            case com.android.internal.R.styleable.TextView_textColor:
622                textColor = a.getColorStateList(attr);
623                break;
624
625            case com.android.internal.R.styleable.TextView_textColorHint:
626                textColorHint = a.getColorStateList(attr);
627                break;
628
629            case com.android.internal.R.styleable.TextView_textColorLink:
630                textColorLink = a.getColorStateList(attr);
631                break;
632
633            case com.android.internal.R.styleable.TextView_textSize:
634                textSize = a.getDimensionPixelSize(attr, textSize);
635                break;
636
637            case com.android.internal.R.styleable.TextView_typeface:
638                typefaceIndex = a.getInt(attr, typefaceIndex);
639                break;
640
641            case com.android.internal.R.styleable.TextView_textStyle:
642                styleIndex = a.getInt(attr, styleIndex);
643                break;
644
645            case com.android.internal.R.styleable.TextView_password:
646                password = a.getBoolean(attr, password);
647                break;
648
649            case com.android.internal.R.styleable.TextView_lineSpacingExtra:
650                mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
651                break;
652
653            case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
654                mSpacingMult = a.getFloat(attr, mSpacingMult);
655                break;
656
657            case com.android.internal.R.styleable.TextView_inputType:
658                inputType = a.getInt(attr, mInputType);
659                break;
660
661            case com.android.internal.R.styleable.TextView_imeOptions:
662                if (mInputContentType == null) {
663                    mInputContentType = new InputContentType();
664                }
665                mInputContentType.imeOptions = a.getInt(attr,
666                        mInputContentType.imeOptions);
667                break;
668
669            case com.android.internal.R.styleable.TextView_imeActionLabel:
670                if (mInputContentType == null) {
671                    mInputContentType = new InputContentType();
672                }
673                mInputContentType.imeActionLabel = a.getText(attr);
674                break;
675
676            case com.android.internal.R.styleable.TextView_imeActionId:
677                if (mInputContentType == null) {
678                    mInputContentType = new InputContentType();
679                }
680                mInputContentType.imeActionId = a.getInt(attr,
681                        mInputContentType.imeActionId);
682                break;
683
684            case com.android.internal.R.styleable.TextView_privateImeOptions:
685                setPrivateImeOptions(a.getString(attr));
686                break;
687
688            case com.android.internal.R.styleable.TextView_editorExtras:
689                try {
690                    setInputExtras(a.getResourceId(attr, 0));
691                } catch (XmlPullParserException e) {
692                    Log.w("TextView", "Failure reading input extras", e);
693                } catch (IOException e) {
694                    Log.w("TextView", "Failure reading input extras", e);
695                }
696                break;
697            }
698        }
699        a.recycle();
700
701        BufferType bufferType = BufferType.EDITABLE;
702
703        if ((inputType&(EditorInfo.TYPE_MASK_CLASS
704                |EditorInfo.TYPE_MASK_VARIATION))
705                == (EditorInfo.TYPE_CLASS_TEXT
706                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
707            password = true;
708        }
709
710        if (inputMethod != null) {
711            Class c;
712
713            try {
714                c = Class.forName(inputMethod.toString());
715            } catch (ClassNotFoundException ex) {
716                throw new RuntimeException(ex);
717            }
718
719            try {
720                mInput = (KeyListener) c.newInstance();
721            } catch (InstantiationException ex) {
722                throw new RuntimeException(ex);
723            } catch (IllegalAccessException ex) {
724                throw new RuntimeException(ex);
725            }
726            try {
727                mInputType = inputType != EditorInfo.TYPE_NULL
728                        ? inputType
729                        : mInput.getInputType();
730            } catch (IncompatibleClassChangeError e) {
731                mInputType = EditorInfo.TYPE_CLASS_TEXT;
732            }
733        } else if (digits != null) {
734            mInput = DigitsKeyListener.getInstance(digits.toString());
735            // If no input type was specified, we will default to generic
736            // text, since we can't tell the IME about the set of digits
737            // that was selected.
738            mInputType = inputType != EditorInfo.TYPE_NULL
739                    ? inputType : EditorInfo.TYPE_CLASS_TEXT;
740        } else if (inputType != EditorInfo.TYPE_NULL) {
741            setInputType(inputType, true);
742            singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
743                            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
744                    (EditorInfo.TYPE_CLASS_TEXT
745                            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
746        } else if (phone) {
747            mInput = DialerKeyListener.getInstance();
748            mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
749        } else if (numeric != 0) {
750            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
751                                                   (numeric & DECIMAL) != 0);
752            inputType = EditorInfo.TYPE_CLASS_NUMBER;
753            if ((numeric & SIGNED) != 0) {
754                inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
755            }
756            if ((numeric & DECIMAL) != 0) {
757                inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
758            }
759            mInputType = inputType;
760        } else if (autotext || autocap != -1) {
761            TextKeyListener.Capitalize cap;
762
763            inputType = EditorInfo.TYPE_CLASS_TEXT;
764            if (!singleLine) {
765                inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
766            }
767
768            switch (autocap) {
769            case 1:
770                cap = TextKeyListener.Capitalize.SENTENCES;
771                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
772                break;
773
774            case 2:
775                cap = TextKeyListener.Capitalize.WORDS;
776                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
777                break;
778
779            case 3:
780                cap = TextKeyListener.Capitalize.CHARACTERS;
781                inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
782                break;
783
784            default:
785                cap = TextKeyListener.Capitalize.NONE;
786                break;
787            }
788
789            mInput = TextKeyListener.getInstance(autotext, cap);
790            mInputType = inputType;
791        } else if (editable) {
792            mInput = TextKeyListener.getInstance();
793            mInputType = EditorInfo.TYPE_CLASS_TEXT;
794        } else {
795            mInput = null;
796
797            switch (buffertype) {
798                case 0:
799                    bufferType = BufferType.NORMAL;
800                    break;
801                case 1:
802                    bufferType = BufferType.SPANNABLE;
803                    break;
804                case 2:
805                    bufferType = BufferType.EDITABLE;
806                    break;
807            }
808        }
809
810        if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS)
811                == EditorInfo.TYPE_CLASS_TEXT) {
812            mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
813                | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
814        }
815
816        if (selectallonfocus) {
817            mSelectAllOnFocus = true;
818
819            if (bufferType == BufferType.NORMAL)
820                bufferType = BufferType.SPANNABLE;
821        }
822
823        setCompoundDrawablesWithIntrinsicBounds(
824            drawableLeft, drawableTop, drawableRight, drawableBottom);
825        setCompoundDrawablePadding(drawablePadding);
826
827        if (singleLine) {
828            setSingleLine();
829
830            if (mInput == null && ellipsize < 0) {
831                ellipsize = 3; // END
832            }
833        }
834
835        switch (ellipsize) {
836            case 1:
837                setEllipsize(TextUtils.TruncateAt.START);
838                break;
839            case 2:
840                setEllipsize(TextUtils.TruncateAt.MIDDLE);
841                break;
842            case 3:
843                setEllipsize(TextUtils.TruncateAt.END);
844                break;
845            case 4:
846                setHorizontalFadingEdgeEnabled(true);
847                setEllipsize(TextUtils.TruncateAt.MARQUEE);
848                break;
849        }
850
851        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
852        setHintTextColor(textColorHint);
853        setLinkTextColor(textColorLink);
854        if (textColorHighlight != 0) {
855            setHighlightColor(textColorHighlight);
856        }
857        setRawTextSize(textSize);
858
859        if (password) {
860            setTransformationMethod(PasswordTransformationMethod.getInstance());
861            typefaceIndex = MONOSPACE;
862        } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS
863                |EditorInfo.TYPE_MASK_VARIATION))
864                == (EditorInfo.TYPE_CLASS_TEXT
865                        |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
866            typefaceIndex = MONOSPACE;
867        }
868
869        setTypefaceByIndex(typefaceIndex, styleIndex);
870
871        if (shadowcolor != 0) {
872            setShadowLayer(r, dx, dy, shadowcolor);
873        }
874
875        if (maxlength >= 0) {
876            setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
877        } else {
878            setFilters(NO_FILTERS);
879        }
880
881        setText(text, bufferType);
882        if (hint != null) setHint(hint);
883
884        /*
885         * Views are not normally focusable unless specified to be.
886         * However, TextViews that have input or movement methods *are*
887         * focusable by default.
888         */
889        a = context.obtainStyledAttributes(attrs,
890                                           com.android.internal.R.styleable.View,
891                                           defStyle, 0);
892
893        boolean focusable = mMovement != null || mInput != null;
894        boolean clickable = focusable;
895        boolean longClickable = focusable;
896
897        n = a.getIndexCount();
898        for (int i = 0; i < n; i++) {
899            int attr = a.getIndex(i);
900
901            switch (attr) {
902            case com.android.internal.R.styleable.View_focusable:
903                focusable = a.getBoolean(attr, focusable);
904                break;
905
906            case com.android.internal.R.styleable.View_clickable:
907                clickable = a.getBoolean(attr, clickable);
908                break;
909
910            case com.android.internal.R.styleable.View_longClickable:
911                longClickable = a.getBoolean(attr, longClickable);
912                break;
913            }
914        }
915        a.recycle();
916
917        setFocusable(focusable);
918        setClickable(clickable);
919        setLongClickable(longClickable);
920    }
921
922    private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
923        Typeface tf = null;
924        switch (typefaceIndex) {
925            case SANS:
926                tf = Typeface.SANS_SERIF;
927                break;
928
929            case SERIF:
930                tf = Typeface.SERIF;
931                break;
932
933            case MONOSPACE:
934                tf = Typeface.MONOSPACE;
935                break;
936        }
937
938        setTypeface(tf, styleIndex);
939    }
940
941    /**
942     * Sets the typeface and style in which the text should be displayed,
943     * and turns on the fake bold and italic bits in the Paint if the
944     * Typeface that you provided does not have all the bits in the
945     * style that you specified.
946     *
947     * @attr ref android.R.styleable#TextView_typeface
948     * @attr ref android.R.styleable#TextView_textStyle
949     */
950    public void setTypeface(Typeface tf, int style) {
951        if (style > 0) {
952            if (tf == null) {
953                tf = Typeface.defaultFromStyle(style);
954            } else {
955                tf = Typeface.create(tf, style);
956            }
957
958            setTypeface(tf);
959            // now compute what (if any) algorithmic styling is needed
960            int typefaceStyle = tf != null ? tf.getStyle() : 0;
961            int need = style & ~typefaceStyle;
962            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
963            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
964        } else {
965            mTextPaint.setFakeBoldText(false);
966            mTextPaint.setTextSkewX(0);
967            setTypeface(tf);
968        }
969    }
970
971    /**
972     * Subclasses override this to specify that they have a KeyListener
973     * by default even if not specifically called for in the XML options.
974     */
975    protected boolean getDefaultEditable() {
976        return false;
977    }
978
979    /**
980     * Subclasses override this to specify a default movement method.
981     */
982    protected MovementMethod getDefaultMovementMethod() {
983        return null;
984    }
985
986    /**
987     * Return the text the TextView is displaying. If setText() was called with
988     * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
989     * the return value from this method to Spannable or Editable, respectively.
990     *
991     * Note: The content of the return value should not be modified. If you want
992     * a modifiable one, you should make your own copy first.
993     */
994    @ViewDebug.CapturedViewProperty
995    public CharSequence getText() {
996        return mText;
997    }
998
999    /**
1000     * Returns the length, in characters, of the text managed by this TextView
1001     */
1002    public int length() {
1003        return mText.length();
1004    }
1005
1006    /**
1007     * Return the text the TextView is displaying as an Editable object.  If
1008     * the text is not editable, null is returned.
1009     *
1010     * @see #getText
1011     */
1012    public Editable getEditableText() {
1013        return (mText instanceof Editable) ? (Editable)mText : null;
1014    }
1015
1016    /**
1017     * @return the height of one standard line in pixels.  Note that markup
1018     * within the text can cause individual lines to be taller or shorter
1019     * than this height, and the layout may contain additional first-
1020     * or last-line padding.
1021     */
1022    public int getLineHeight() {
1023        return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
1024                          + mSpacingAdd);
1025    }
1026
1027    /**
1028     * @return the Layout that is currently being used to display the text.
1029     * This can be null if the text or width has recently changes.
1030     */
1031    public final Layout getLayout() {
1032        return mLayout;
1033    }
1034
1035    /**
1036     * @return the current key listener for this TextView.
1037     * This will frequently be null for non-EditText TextViews.
1038     */
1039    public final KeyListener getKeyListener() {
1040        return mInput;
1041    }
1042
1043    /**
1044     * Sets the key listener to be used with this TextView.  This can be null
1045     * to disallow user input.  Note that this method has significant and
1046     * subtle interactions with soft keyboards and other input method:
1047     * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1048     * for important details.  Calling this method will replace the current
1049     * content type of the text view with the content type returned by the
1050     * key listener.
1051     * <p>
1052     * Be warned that if you want a TextView with a key listener or movement
1053     * method not to be focusable, or if you want a TextView without a
1054     * key listener or movement method to be focusable, you must call
1055     * {@link #setFocusable} again after calling this to get the focusability
1056     * back the way you want it.
1057     *
1058     * @attr ref android.R.styleable#TextView_numeric
1059     * @attr ref android.R.styleable#TextView_digits
1060     * @attr ref android.R.styleable#TextView_phoneNumber
1061     * @attr ref android.R.styleable#TextView_inputMethod
1062     * @attr ref android.R.styleable#TextView_capitalize
1063     * @attr ref android.R.styleable#TextView_autoText
1064     */
1065    public void setKeyListener(KeyListener input) {
1066        setKeyListenerOnly(input);
1067        fixFocusableAndClickableSettings();
1068
1069        if (input != null) {
1070            try {
1071                mInputType = mInput.getInputType();
1072            } catch (IncompatibleClassChangeError e) {
1073                mInputType = EditorInfo.TYPE_CLASS_TEXT;
1074            }
1075            if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
1076                    == EditorInfo.TYPE_CLASS_TEXT) {
1077                if (mSingleLine) {
1078                    mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
1079                } else {
1080                    mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
1081                }
1082            }
1083        } else {
1084            mInputType = EditorInfo.TYPE_NULL;
1085        }
1086
1087        InputMethodManager imm = InputMethodManager.peekInstance();
1088        if (imm != null) imm.restartInput(this);
1089    }
1090
1091    private void setKeyListenerOnly(KeyListener input) {
1092        mInput = input;
1093        if (mInput != null && !(mText instanceof Editable))
1094            setText(mText);
1095
1096        setFilters((Editable) mText, mFilters);
1097    }
1098
1099    /**
1100     * @return the movement method being used for this TextView.
1101     * This will frequently be null for non-EditText TextViews.
1102     */
1103    public final MovementMethod getMovementMethod() {
1104        return mMovement;
1105    }
1106
1107    /**
1108     * Sets the movement method (arrow key handler) to be used for
1109     * this TextView.  This can be null to disallow using the arrow keys
1110     * to move the cursor or scroll the view.
1111     * <p>
1112     * Be warned that if you want a TextView with a key listener or movement
1113     * method not to be focusable, or if you want a TextView without a
1114     * key listener or movement method to be focusable, you must call
1115     * {@link #setFocusable} again after calling this to get the focusability
1116     * back the way you want it.
1117     */
1118    public final void setMovementMethod(MovementMethod movement) {
1119        mMovement = movement;
1120
1121        if (mMovement != null && !(mText instanceof Spannable))
1122            setText(mText);
1123
1124        fixFocusableAndClickableSettings();
1125    }
1126
1127    private void fixFocusableAndClickableSettings() {
1128        if ((mMovement != null) || mInput != null) {
1129            setFocusable(true);
1130            setClickable(true);
1131            setLongClickable(true);
1132        } else {
1133            setFocusable(false);
1134            setClickable(false);
1135            setLongClickable(false);
1136        }
1137    }
1138
1139    /**
1140     * @return the current transformation method for this TextView.
1141     * This will frequently be null except for single-line and password
1142     * fields.
1143     */
1144    public final TransformationMethod getTransformationMethod() {
1145        return mTransformation;
1146    }
1147
1148    /**
1149     * Sets the transformation that is applied to the text that this
1150     * TextView is displaying.
1151     *
1152     * @attr ref android.R.styleable#TextView_password
1153     * @attr ref android.R.styleable#TextView_singleLine
1154     */
1155    public final void setTransformationMethod(TransformationMethod method) {
1156        if (method == mTransformation) {
1157            // Avoid the setText() below if the transformation is
1158            // the same.
1159            return;
1160        }
1161        if (mTransformation != null) {
1162            if (mText instanceof Spannable) {
1163                ((Spannable) mText).removeSpan(mTransformation);
1164            }
1165        }
1166
1167        mTransformation = method;
1168
1169        setText(mText);
1170    }
1171
1172    /**
1173     * Returns the top padding of the view, plus space for the top
1174     * Drawable if any.
1175     */
1176    public int getCompoundPaddingTop() {
1177        final Drawables dr = mDrawables;
1178        if (dr == null || dr.mDrawableTop == null) {
1179            return mPaddingTop;
1180        } else {
1181            return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1182        }
1183    }
1184
1185    /**
1186     * Returns the bottom padding of the view, plus space for the bottom
1187     * Drawable if any.
1188     */
1189    public int getCompoundPaddingBottom() {
1190        final Drawables dr = mDrawables;
1191        if (dr == null || dr.mDrawableBottom == null) {
1192            return mPaddingBottom;
1193        } else {
1194            return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1195        }
1196    }
1197
1198    /**
1199     * Returns the left padding of the view, plus space for the left
1200     * Drawable if any.
1201     */
1202    public int getCompoundPaddingLeft() {
1203        final Drawables dr = mDrawables;
1204        if (dr == null || dr.mDrawableLeft == null) {
1205            return mPaddingLeft;
1206        } else {
1207            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1208        }
1209    }
1210
1211    /**
1212     * Returns the right padding of the view, plus space for the right
1213     * Drawable if any.
1214     */
1215    public int getCompoundPaddingRight() {
1216        final Drawables dr = mDrawables;
1217        if (dr == null || dr.mDrawableRight == null) {
1218            return mPaddingRight;
1219        } else {
1220            return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1221        }
1222    }
1223
1224    /**
1225     * Returns the extended top padding of the view, including both the
1226     * top Drawable if any and any extra space to keep more than maxLines
1227     * of text from showing.  It is only valid to call this after measuring.
1228     */
1229    public int getExtendedPaddingTop() {
1230        if (mMaxMode != LINES) {
1231            return getCompoundPaddingTop();
1232        }
1233
1234        if (mLayout.getLineCount() <= mMaximum) {
1235            return getCompoundPaddingTop();
1236        }
1237
1238        int top = getCompoundPaddingTop();
1239        int bottom = getCompoundPaddingBottom();
1240        int viewht = getHeight() - top - bottom;
1241        int layoutht = mLayout.getLineTop(mMaximum);
1242
1243        if (layoutht >= viewht) {
1244            return top;
1245        }
1246
1247        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1248        if (gravity == Gravity.TOP) {
1249            return top;
1250        } else if (gravity == Gravity.BOTTOM) {
1251            return top + viewht - layoutht;
1252        } else { // (gravity == Gravity.CENTER_VERTICAL)
1253            return top + (viewht - layoutht) / 2;
1254        }
1255    }
1256
1257    /**
1258     * Returns the extended bottom padding of the view, including both the
1259     * bottom Drawable if any and any extra space to keep more than maxLines
1260     * of text from showing.  It is only valid to call this after measuring.
1261     */
1262    public int getExtendedPaddingBottom() {
1263        if (mMaxMode != LINES) {
1264            return getCompoundPaddingBottom();
1265        }
1266
1267        if (mLayout.getLineCount() <= mMaximum) {
1268            return getCompoundPaddingBottom();
1269        }
1270
1271        int top = getCompoundPaddingTop();
1272        int bottom = getCompoundPaddingBottom();
1273        int viewht = getHeight() - top - bottom;
1274        int layoutht = mLayout.getLineTop(mMaximum);
1275
1276        if (layoutht >= viewht) {
1277            return bottom;
1278        }
1279
1280        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1281        if (gravity == Gravity.TOP) {
1282            return bottom + viewht - layoutht;
1283        } else if (gravity == Gravity.BOTTOM) {
1284            return bottom;
1285        } else { // (gravity == Gravity.CENTER_VERTICAL)
1286            return bottom + (viewht - layoutht) / 2;
1287        }
1288    }
1289
1290    /**
1291     * Returns the total left padding of the view, including the left
1292     * Drawable if any.
1293     */
1294    public int getTotalPaddingLeft() {
1295        return getCompoundPaddingLeft();
1296    }
1297
1298    /**
1299     * Returns the total right padding of the view, including the right
1300     * Drawable if any.
1301     */
1302    public int getTotalPaddingRight() {
1303        return getCompoundPaddingRight();
1304    }
1305
1306    /**
1307     * Returns the total top padding of the view, including the top
1308     * Drawable if any, the extra space to keep more than maxLines
1309     * from showing, and the vertical offset for gravity, if any.
1310     */
1311    public int getTotalPaddingTop() {
1312        return getExtendedPaddingTop() + getVerticalOffset(true);
1313    }
1314
1315    /**
1316     * Returns the total bottom padding of the view, including the bottom
1317     * Drawable if any, the extra space to keep more than maxLines
1318     * from showing, and the vertical offset for gravity, if any.
1319     */
1320    public int getTotalPaddingBottom() {
1321        return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1322    }
1323
1324    /**
1325     * Sets the Drawables (if any) to appear to the left of, above,
1326     * to the right of, and below the text.  Use null if you do not
1327     * want a Drawable there.  The Drawables must already have had
1328     * {@link Drawable#setBounds} called.
1329     *
1330     * @attr ref android.R.styleable#TextView_drawableLeft
1331     * @attr ref android.R.styleable#TextView_drawableTop
1332     * @attr ref android.R.styleable#TextView_drawableRight
1333     * @attr ref android.R.styleable#TextView_drawableBottom
1334     */
1335    public void setCompoundDrawables(Drawable left, Drawable top,
1336                                     Drawable right, Drawable bottom) {
1337        Drawables dr = mDrawables;
1338
1339        final boolean drawables = left != null || top != null
1340                || right != null || bottom != null;
1341
1342        if (!drawables) {
1343            // Clearing drawables...  can we free the data structure?
1344            if (dr != null) {
1345                if (dr.mDrawablePadding == 0) {
1346                    mDrawables = null;
1347                } else {
1348                    // We need to retain the last set padding, so just clear
1349                    // out all of the fields in the existing structure.
1350                    if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
1351                    dr.mDrawableLeft = null;
1352                    if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
1353                    dr.mDrawableTop = null;
1354                    if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
1355                    dr.mDrawableRight = null;
1356                    if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
1357                    dr.mDrawableBottom = null;
1358                    dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1359                    dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1360                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1361                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1362                }
1363            }
1364        } else {
1365            if (dr == null) {
1366                mDrawables = dr = new Drawables();
1367            }
1368
1369            if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1370                dr.mDrawableLeft.setCallback(null);
1371            }
1372            dr.mDrawableLeft = left;
1373            if (dr.mDrawableTop != left && dr.mDrawableTop != null) {
1374                dr.mDrawableTop.setCallback(null);
1375            }
1376            dr.mDrawableTop = top;
1377            if (dr.mDrawableRight != left && dr.mDrawableRight != null) {
1378                dr.mDrawableRight.setCallback(null);
1379            }
1380            dr.mDrawableRight = right;
1381            if (dr.mDrawableBottom != left && dr.mDrawableBottom != null) {
1382                dr.mDrawableBottom.setCallback(null);
1383            }
1384            dr.mDrawableBottom = bottom;
1385
1386            final Rect compoundRect = dr.mCompoundRect;
1387            int[] state;
1388
1389            state = getDrawableState();
1390
1391            if (left != null) {
1392                left.setState(state);
1393                left.copyBounds(compoundRect);
1394                left.setCallback(this);
1395                dr.mDrawableSizeLeft = compoundRect.width();
1396                dr.mDrawableHeightLeft = compoundRect.height();
1397            } else {
1398                dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1399            }
1400
1401            if (right != null) {
1402                right.setState(state);
1403                right.copyBounds(compoundRect);
1404                right.setCallback(this);
1405                dr.mDrawableSizeRight = compoundRect.width();
1406                dr.mDrawableHeightRight = compoundRect.height();
1407            } else {
1408                dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1409            }
1410
1411            if (top != null) {
1412                top.setState(state);
1413                top.copyBounds(compoundRect);
1414                top.setCallback(this);
1415                dr.mDrawableSizeTop = compoundRect.height();
1416                dr.mDrawableWidthTop = compoundRect.width();
1417            } else {
1418                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1419            }
1420
1421            if (bottom != null) {
1422                bottom.setState(state);
1423                bottom.copyBounds(compoundRect);
1424                bottom.setCallback(this);
1425                dr.mDrawableSizeBottom = compoundRect.height();
1426                dr.mDrawableWidthBottom = compoundRect.width();
1427            } else {
1428                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1429            }
1430        }
1431
1432        invalidate();
1433        requestLayout();
1434    }
1435
1436    /**
1437     * Sets the Drawables (if any) to appear to the left of, above,
1438     * to the right of, and below the text.  Use 0 if you do not
1439     * want a Drawable there. The Drawables' bounds will be set to
1440     * their intrinsic bounds.
1441     *
1442     * @param left Resource identifier of the left Drawable.
1443     * @param top Resource identifier of the top Drawable.
1444     * @param right Resource identifier of the right Drawable.
1445     * @param bottom Resource identifier of the bottom Drawable.
1446     *
1447     * @attr ref android.R.styleable#TextView_drawableLeft
1448     * @attr ref android.R.styleable#TextView_drawableTop
1449     * @attr ref android.R.styleable#TextView_drawableRight
1450     * @attr ref android.R.styleable#TextView_drawableBottom
1451     */
1452    public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1453        final Resources resources = getContext().getResources();
1454        setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
1455                top != 0 ? resources.getDrawable(top) : null,
1456                right != 0 ? resources.getDrawable(right) : null,
1457                bottom != 0 ? resources.getDrawable(bottom) : null);
1458    }
1459
1460    /**
1461     * Sets the Drawables (if any) to appear to the left of, above,
1462     * to the right of, and below the text.  Use null if you do not
1463     * want a Drawable there. The Drawables' bounds will be set to
1464     * their intrinsic bounds.
1465     *
1466     * @attr ref android.R.styleable#TextView_drawableLeft
1467     * @attr ref android.R.styleable#TextView_drawableTop
1468     * @attr ref android.R.styleable#TextView_drawableRight
1469     * @attr ref android.R.styleable#TextView_drawableBottom
1470     */
1471    public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1472            Drawable right, Drawable bottom) {
1473
1474        if (left != null) {
1475            left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1476        }
1477        if (right != null) {
1478            right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1479        }
1480        if (top != null) {
1481            top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1482        }
1483        if (bottom != null) {
1484            bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1485        }
1486        setCompoundDrawables(left, top, right, bottom);
1487    }
1488
1489    /**
1490     * Returns drawables for the left, top, right, and bottom borders.
1491     */
1492    public Drawable[] getCompoundDrawables() {
1493        final Drawables dr = mDrawables;
1494        if (dr != null) {
1495            return new Drawable[] {
1496                dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
1497            };
1498        } else {
1499            return new Drawable[] { null, null, null, null };
1500        }
1501    }
1502
1503    /**
1504     * Sets the size of the padding between the compound drawables and
1505     * the text.
1506     *
1507     * @attr ref android.R.styleable#TextView_drawablePadding
1508     */
1509    public void setCompoundDrawablePadding(int pad) {
1510        Drawables dr = mDrawables;
1511        if (pad == 0) {
1512            if (dr != null) {
1513                dr.mDrawablePadding = pad;
1514            }
1515        } else {
1516            if (dr == null) {
1517                mDrawables = dr = new Drawables();
1518            }
1519            dr.mDrawablePadding = pad;
1520        }
1521
1522        invalidate();
1523        requestLayout();
1524    }
1525
1526    /**
1527     * Returns the padding between the compound drawables and the text.
1528     */
1529    public int getCompoundDrawablePadding() {
1530        final Drawables dr = mDrawables;
1531        return dr != null ? dr.mDrawablePadding : 0;
1532    }
1533
1534    @Override
1535    public void setPadding(int left, int top, int right, int bottom) {
1536        if (left != mPaddingLeft ||
1537            right != mPaddingRight ||
1538            top != mPaddingTop ||
1539            bottom != mPaddingBottom) {
1540            nullLayouts();
1541        }
1542
1543        // the super call will requestLayout()
1544        super.setPadding(left, top, right, bottom);
1545        invalidate();
1546    }
1547
1548    /**
1549     * Gets the autolink mask of the text.  See {@link
1550     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1551     * possible values.
1552     *
1553     * @attr ref android.R.styleable#TextView_autoLink
1554     */
1555    public final int getAutoLinkMask() {
1556        return mAutoLinkMask;
1557    }
1558
1559    /**
1560     * Sets the text color, size, style, hint color, and highlight color
1561     * from the specified TextAppearance resource.
1562     */
1563    public void setTextAppearance(Context context, int resid) {
1564        TypedArray appearance =
1565            context.obtainStyledAttributes(resid,
1566                                           com.android.internal.R.styleable.TextAppearance);
1567
1568        int color;
1569        ColorStateList colors;
1570        int ts;
1571
1572        color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
1573        if (color != 0) {
1574            setHighlightColor(color);
1575        }
1576
1577        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1578                                              TextAppearance_textColor);
1579        if (colors != null) {
1580            setTextColor(colors);
1581        }
1582
1583        ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
1584                                              TextAppearance_textSize, 0);
1585        if (ts != 0) {
1586            setRawTextSize(ts);
1587        }
1588
1589        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1590                                              TextAppearance_textColorHint);
1591        if (colors != null) {
1592            setHintTextColor(colors);
1593        }
1594
1595        colors = appearance.getColorStateList(com.android.internal.R.styleable.
1596                                              TextAppearance_textColorLink);
1597        if (colors != null) {
1598            setLinkTextColor(colors);
1599        }
1600
1601        int typefaceIndex, styleIndex;
1602
1603        typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
1604                                          TextAppearance_typeface, -1);
1605        styleIndex = appearance.getInt(com.android.internal.R.styleable.
1606                                       TextAppearance_textStyle, -1);
1607
1608        setTypefaceByIndex(typefaceIndex, styleIndex);
1609        appearance.recycle();
1610    }
1611
1612    /**
1613     * @return the size (in pixels) of the default text size in this TextView.
1614     */
1615    public float getTextSize() {
1616        return mTextPaint.getTextSize();
1617    }
1618
1619    /**
1620     * Set the default text size to the given value, interpreted as "scaled
1621     * pixel" units.  This size is adjusted based on the current density and
1622     * user font size preference.
1623     *
1624     * @param size The scaled pixel size.
1625     *
1626     * @attr ref android.R.styleable#TextView_textSize
1627     */
1628    @android.view.RemotableViewMethod
1629    public void setTextSize(float size) {
1630        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
1631    }
1632
1633    /**
1634     * Set the default text size to a given unit and value.  See {@link
1635     * TypedValue} for the possible dimension units.
1636     *
1637     * @param unit The desired dimension unit.
1638     * @param size The desired size in the given units.
1639     *
1640     * @attr ref android.R.styleable#TextView_textSize
1641     */
1642    public void setTextSize(int unit, float size) {
1643        Context c = getContext();
1644        Resources r;
1645
1646        if (c == null)
1647            r = Resources.getSystem();
1648        else
1649            r = c.getResources();
1650
1651        setRawTextSize(TypedValue.applyDimension(
1652            unit, size, r.getDisplayMetrics()));
1653    }
1654
1655    private void setRawTextSize(float size) {
1656        if (size != mTextPaint.getTextSize()) {
1657            mTextPaint.setTextSize(size);
1658
1659            if (mLayout != null) {
1660                nullLayouts();
1661                requestLayout();
1662                invalidate();
1663            }
1664        }
1665    }
1666
1667    /**
1668     * @return the extent by which text is currently being stretched
1669     * horizontally.  This will usually be 1.
1670     */
1671    public float getTextScaleX() {
1672        return mTextPaint.getTextScaleX();
1673    }
1674
1675    /**
1676     * Sets the extent by which text should be stretched horizontally.
1677     *
1678     * @attr ref android.R.styleable#TextView_textScaleX
1679     */
1680    @android.view.RemotableViewMethod
1681    public void setTextScaleX(float size) {
1682        if (size != mTextPaint.getTextScaleX()) {
1683            mUserSetTextScaleX = true;
1684            mTextPaint.setTextScaleX(size);
1685
1686            if (mLayout != null) {
1687                nullLayouts();
1688                requestLayout();
1689                invalidate();
1690            }
1691        }
1692    }
1693
1694    /**
1695     * Sets the typeface and style in which the text should be displayed.
1696     * Note that not all Typeface families actually have bold and italic
1697     * variants, so you may need to use
1698     * {@link #setTypeface(Typeface, int)} to get the appearance
1699     * that you actually want.
1700     *
1701     * @attr ref android.R.styleable#TextView_typeface
1702     * @attr ref android.R.styleable#TextView_textStyle
1703     */
1704    public void setTypeface(Typeface tf) {
1705        if (mTextPaint.getTypeface() != tf) {
1706            mTextPaint.setTypeface(tf);
1707
1708            if (mLayout != null) {
1709                nullLayouts();
1710                requestLayout();
1711                invalidate();
1712            }
1713        }
1714    }
1715
1716    /**
1717     * @return the current typeface and style in which the text is being
1718     * displayed.
1719     */
1720    public Typeface getTypeface() {
1721        return mTextPaint.getTypeface();
1722    }
1723
1724    /**
1725     * Sets the text color for all the states (normal, selected,
1726     * focused) to be this color.
1727     *
1728     * @attr ref android.R.styleable#TextView_textColor
1729     */
1730    @android.view.RemotableViewMethod
1731    public void setTextColor(int color) {
1732        mTextColor = ColorStateList.valueOf(color);
1733        updateTextColors();
1734    }
1735
1736    /**
1737     * Sets the text color.
1738     *
1739     * @attr ref android.R.styleable#TextView_textColor
1740     */
1741    public void setTextColor(ColorStateList colors) {
1742        if (colors == null) {
1743            throw new NullPointerException();
1744        }
1745
1746        mTextColor = colors;
1747        updateTextColors();
1748    }
1749
1750    /**
1751     * Return the set of text colors.
1752     *
1753     * @return Returns the set of text colors.
1754     */
1755    public final ColorStateList getTextColors() {
1756        return mTextColor;
1757    }
1758
1759    /**
1760     * <p>Return the current color selected for normal text.</p>
1761     *
1762     * @return Returns the current text color.
1763     */
1764    public final int getCurrentTextColor() {
1765        return mCurTextColor;
1766    }
1767
1768    /**
1769     * Sets the color used to display the selection highlight.
1770     *
1771     * @attr ref android.R.styleable#TextView_textColorHighlight
1772     */
1773    @android.view.RemotableViewMethod
1774    public void setHighlightColor(int color) {
1775        if (mHighlightColor != color) {
1776            mHighlightColor = color;
1777            invalidate();
1778        }
1779    }
1780
1781    /**
1782     * Gives the text a shadow of the specified radius and color, the specified
1783     * distance from its normal position.
1784     *
1785     * @attr ref android.R.styleable#TextView_shadowColor
1786     * @attr ref android.R.styleable#TextView_shadowDx
1787     * @attr ref android.R.styleable#TextView_shadowDy
1788     * @attr ref android.R.styleable#TextView_shadowRadius
1789     */
1790    public void setShadowLayer(float radius, float dx, float dy, int color) {
1791        mTextPaint.setShadowLayer(radius, dx, dy, color);
1792
1793        mShadowRadius = radius;
1794        mShadowDx = dx;
1795        mShadowDy = dy;
1796
1797        invalidate();
1798    }
1799
1800    /**
1801     * @return the base paint used for the text.  Please use this only to
1802     * consult the Paint's properties and not to change them.
1803     */
1804    public TextPaint getPaint() {
1805        return mTextPaint;
1806    }
1807
1808    /**
1809     * Sets the autolink mask of the text.  See {@link
1810     * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1811     * possible values.
1812     *
1813     * @attr ref android.R.styleable#TextView_autoLink
1814     */
1815    @android.view.RemotableViewMethod
1816    public final void setAutoLinkMask(int mask) {
1817        mAutoLinkMask = mask;
1818    }
1819
1820    /**
1821     * Sets whether the movement method will automatically be set to
1822     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1823     * set to nonzero and links are detected in {@link #setText}.
1824     * The default is true.
1825     *
1826     * @attr ref android.R.styleable#TextView_linksClickable
1827     */
1828    @android.view.RemotableViewMethod
1829    public final void setLinksClickable(boolean whether) {
1830        mLinksClickable = whether;
1831    }
1832
1833    /**
1834     * Returns whether the movement method will automatically be set to
1835     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1836     * set to nonzero and links are detected in {@link #setText}.
1837     * The default is true.
1838     *
1839     * @attr ref android.R.styleable#TextView_linksClickable
1840     */
1841    public final boolean getLinksClickable() {
1842        return mLinksClickable;
1843    }
1844
1845    /**
1846     * Returns the list of URLSpans attached to the text
1847     * (by {@link Linkify} or otherwise) if any.  You can call
1848     * {@link URLSpan#getURL} on them to find where they link to
1849     * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
1850     * to find the region of the text they are attached to.
1851     */
1852    public URLSpan[] getUrls() {
1853        if (mText instanceof Spanned) {
1854            return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
1855        } else {
1856            return new URLSpan[0];
1857        }
1858    }
1859
1860    /**
1861     * Sets the color of the hint text.
1862     *
1863     * @attr ref android.R.styleable#TextView_textColorHint
1864     */
1865    @android.view.RemotableViewMethod
1866    public final void setHintTextColor(int color) {
1867        mHintTextColor = ColorStateList.valueOf(color);
1868        updateTextColors();
1869    }
1870
1871    /**
1872     * Sets the color of the hint text.
1873     *
1874     * @attr ref android.R.styleable#TextView_textColorHint
1875     */
1876    public final void setHintTextColor(ColorStateList colors) {
1877        mHintTextColor = colors;
1878        updateTextColors();
1879    }
1880
1881    /**
1882     * <p>Return the color used to paint the hint text.</p>
1883     *
1884     * @return Returns the list of hint text colors.
1885     */
1886    public final ColorStateList getHintTextColors() {
1887        return mHintTextColor;
1888    }
1889
1890    /**
1891     * <p>Return the current color selected to paint the hint text.</p>
1892     *
1893     * @return Returns the current hint text color.
1894     */
1895    public final int getCurrentHintTextColor() {
1896        return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
1897    }
1898
1899    /**
1900     * Sets the color of links in the text.
1901     *
1902     * @attr ref android.R.styleable#TextView_textColorLink
1903     */
1904    @android.view.RemotableViewMethod
1905    public final void setLinkTextColor(int color) {
1906        mLinkTextColor = ColorStateList.valueOf(color);
1907        updateTextColors();
1908    }
1909
1910    /**
1911     * Sets the color of links in the text.
1912     *
1913     * @attr ref android.R.styleable#TextView_textColorLink
1914     */
1915    public final void setLinkTextColor(ColorStateList colors) {
1916        mLinkTextColor = colors;
1917        updateTextColors();
1918    }
1919
1920    /**
1921     * <p>Returns the color used to paint links in the text.</p>
1922     *
1923     * @return Returns the list of link text colors.
1924     */
1925    public final ColorStateList getLinkTextColors() {
1926        return mLinkTextColor;
1927    }
1928
1929    /**
1930     * Sets the horizontal alignment of the text and the
1931     * vertical gravity that will be used when there is extra space
1932     * in the TextView beyond what is required for the text itself.
1933     *
1934     * @see android.view.Gravity
1935     * @attr ref android.R.styleable#TextView_gravity
1936     */
1937    public void setGravity(int gravity) {
1938        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
1939            gravity |= Gravity.LEFT;
1940        }
1941        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
1942            gravity |= Gravity.TOP;
1943        }
1944
1945        boolean newLayout = false;
1946
1947        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
1948            (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
1949            newLayout = true;
1950        }
1951
1952        if (gravity != mGravity) {
1953            invalidate();
1954        }
1955
1956        mGravity = gravity;
1957
1958        if (mLayout != null && newLayout) {
1959            // XXX this is heavy-handed because no actual content changes.
1960            int want = mLayout.getWidth();
1961            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
1962
1963            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
1964                          mRight - mLeft - getCompoundPaddingLeft() -
1965                          getCompoundPaddingRight(), true);
1966        }
1967    }
1968
1969    /**
1970     * Returns the horizontal and vertical alignment of this TextView.
1971     *
1972     * @see android.view.Gravity
1973     * @attr ref android.R.styleable#TextView_gravity
1974     */
1975    public int getGravity() {
1976        return mGravity;
1977    }
1978
1979    /**
1980     * @return the flags on the Paint being used to display the text.
1981     * @see Paint#getFlags
1982     */
1983    public int getPaintFlags() {
1984        return mTextPaint.getFlags();
1985    }
1986
1987    /**
1988     * Sets flags on the Paint being used to display the text and
1989     * reflows the text if they are different from the old flags.
1990     * @see Paint#setFlags
1991     */
1992    @android.view.RemotableViewMethod
1993    public void setPaintFlags(int flags) {
1994        if (mTextPaint.getFlags() != flags) {
1995            mTextPaint.setFlags(flags);
1996
1997            if (mLayout != null) {
1998                nullLayouts();
1999                requestLayout();
2000                invalidate();
2001            }
2002        }
2003    }
2004
2005    /**
2006     * Sets whether the text should be allowed to be wider than the
2007     * View is.  If false, it will be wrapped to the width of the View.
2008     *
2009     * @attr ref android.R.styleable#TextView_scrollHorizontally
2010     */
2011    public void setHorizontallyScrolling(boolean whether) {
2012        mHorizontallyScrolling = whether;
2013
2014        if (mLayout != null) {
2015            nullLayouts();
2016            requestLayout();
2017            invalidate();
2018        }
2019    }
2020
2021    /**
2022     * Makes the TextView at least this many lines tall
2023     *
2024     * @attr ref android.R.styleable#TextView_minLines
2025     */
2026    @android.view.RemotableViewMethod
2027    public void setMinLines(int minlines) {
2028        mMinimum = minlines;
2029        mMinMode = LINES;
2030
2031        requestLayout();
2032        invalidate();
2033    }
2034
2035    /**
2036     * Makes the TextView at least this many pixels tall
2037     *
2038     * @attr ref android.R.styleable#TextView_minHeight
2039     */
2040    @android.view.RemotableViewMethod
2041    public void setMinHeight(int minHeight) {
2042        mMinimum = minHeight;
2043        mMinMode = PIXELS;
2044
2045        requestLayout();
2046        invalidate();
2047    }
2048
2049    /**
2050     * Makes the TextView at most this many lines tall
2051     *
2052     * @attr ref android.R.styleable#TextView_maxLines
2053     */
2054    @android.view.RemotableViewMethod
2055    public void setMaxLines(int maxlines) {
2056        mMaximum = maxlines;
2057        mMaxMode = LINES;
2058
2059        requestLayout();
2060        invalidate();
2061    }
2062
2063    /**
2064     * Makes the TextView at most this many pixels tall
2065     *
2066     * @attr ref android.R.styleable#TextView_maxHeight
2067     */
2068    @android.view.RemotableViewMethod
2069    public void setMaxHeight(int maxHeight) {
2070        mMaximum = maxHeight;
2071        mMaxMode = PIXELS;
2072
2073        requestLayout();
2074        invalidate();
2075    }
2076
2077    /**
2078     * Makes the TextView exactly this many lines tall
2079     *
2080     * @attr ref android.R.styleable#TextView_lines
2081     */
2082    @android.view.RemotableViewMethod
2083    public void setLines(int lines) {
2084        mMaximum = mMinimum = lines;
2085        mMaxMode = mMinMode = LINES;
2086
2087        requestLayout();
2088        invalidate();
2089    }
2090
2091    /**
2092     * Makes the TextView exactly this many pixels tall.
2093     * You could do the same thing by specifying this number in the
2094     * LayoutParams.
2095     *
2096     * @attr ref android.R.styleable#TextView_height
2097     */
2098    @android.view.RemotableViewMethod
2099    public void setHeight(int pixels) {
2100        mMaximum = mMinimum = pixels;
2101        mMaxMode = mMinMode = PIXELS;
2102
2103        requestLayout();
2104        invalidate();
2105    }
2106
2107    /**
2108     * Makes the TextView at least this many ems wide
2109     *
2110     * @attr ref android.R.styleable#TextView_minEms
2111     */
2112    @android.view.RemotableViewMethod
2113    public void setMinEms(int minems) {
2114        mMinWidth = minems;
2115        mMinWidthMode = EMS;
2116
2117        requestLayout();
2118        invalidate();
2119    }
2120
2121    /**
2122     * Makes the TextView at least this many pixels wide
2123     *
2124     * @attr ref android.R.styleable#TextView_minWidth
2125     */
2126    @android.view.RemotableViewMethod
2127    public void setMinWidth(int minpixels) {
2128        mMinWidth = minpixels;
2129        mMinWidthMode = PIXELS;
2130
2131        requestLayout();
2132        invalidate();
2133    }
2134
2135    /**
2136     * Makes the TextView at most this many ems wide
2137     *
2138     * @attr ref android.R.styleable#TextView_maxEms
2139     */
2140    @android.view.RemotableViewMethod
2141    public void setMaxEms(int maxems) {
2142        mMaxWidth = maxems;
2143        mMaxWidthMode = EMS;
2144
2145        requestLayout();
2146        invalidate();
2147    }
2148
2149    /**
2150     * Makes the TextView at most this many pixels wide
2151     *
2152     * @attr ref android.R.styleable#TextView_maxWidth
2153     */
2154    @android.view.RemotableViewMethod
2155    public void setMaxWidth(int maxpixels) {
2156        mMaxWidth = maxpixels;
2157        mMaxWidthMode = PIXELS;
2158
2159        requestLayout();
2160        invalidate();
2161    }
2162
2163    /**
2164     * Makes the TextView exactly this many ems wide
2165     *
2166     * @attr ref android.R.styleable#TextView_ems
2167     */
2168    @android.view.RemotableViewMethod
2169    public void setEms(int ems) {
2170        mMaxWidth = mMinWidth = ems;
2171        mMaxWidthMode = mMinWidthMode = EMS;
2172
2173        requestLayout();
2174        invalidate();
2175    }
2176
2177    /**
2178     * Makes the TextView exactly this many pixels wide.
2179     * You could do the same thing by specifying this number in the
2180     * LayoutParams.
2181     *
2182     * @attr ref android.R.styleable#TextView_width
2183     */
2184    @android.view.RemotableViewMethod
2185    public void setWidth(int pixels) {
2186        mMaxWidth = mMinWidth = pixels;
2187        mMaxWidthMode = mMinWidthMode = PIXELS;
2188
2189        requestLayout();
2190        invalidate();
2191    }
2192
2193
2194    /**
2195     * Sets line spacing for this TextView.  Each line will have its height
2196     * multiplied by <code>mult</code> and have <code>add</code> added to it.
2197     *
2198     * @attr ref android.R.styleable#TextView_lineSpacingExtra
2199     * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
2200     */
2201    public void setLineSpacing(float add, float mult) {
2202        mSpacingMult = mult;
2203        mSpacingAdd = add;
2204
2205        if (mLayout != null) {
2206            nullLayouts();
2207            requestLayout();
2208            invalidate();
2209        }
2210    }
2211
2212    /**
2213     * Convenience method: Append the specified text to the TextView's
2214     * display buffer, upgrading it to BufferType.EDITABLE if it was
2215     * not already editable.
2216     */
2217    public final void append(CharSequence text) {
2218        append(text, 0, text.length());
2219    }
2220
2221    /**
2222     * Convenience method: Append the specified text slice to the TextView's
2223     * display buffer, upgrading it to BufferType.EDITABLE if it was
2224     * not already editable.
2225     */
2226    public void append(CharSequence text, int start, int end) {
2227        if (!(mText instanceof Editable)) {
2228            setText(mText, BufferType.EDITABLE);
2229        }
2230
2231        ((Editable) mText).append(text, start, end);
2232    }
2233
2234    private void updateTextColors() {
2235        boolean inval = false;
2236        int color = mTextColor.getColorForState(getDrawableState(), 0);
2237        if (color != mCurTextColor) {
2238            mCurTextColor = color;
2239            inval = true;
2240        }
2241        if (mLinkTextColor != null) {
2242            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
2243            if (color != mTextPaint.linkColor) {
2244                mTextPaint.linkColor = color;
2245                inval = true;
2246            }
2247        }
2248        if (mHintTextColor != null) {
2249            color = mHintTextColor.getColorForState(getDrawableState(), 0);
2250            if (color != mCurHintTextColor && mText.length() == 0) {
2251                mCurHintTextColor = color;
2252                inval = true;
2253            }
2254        }
2255        if (inval) {
2256            invalidate();
2257        }
2258    }
2259
2260    @Override
2261    protected void drawableStateChanged() {
2262        super.drawableStateChanged();
2263        if (mTextColor != null && mTextColor.isStateful()
2264                || (mHintTextColor != null && mHintTextColor.isStateful())
2265                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
2266            updateTextColors();
2267        }
2268
2269        final Drawables dr = mDrawables;
2270        if (dr != null) {
2271            int[] state = getDrawableState();
2272            if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
2273                dr.mDrawableTop.setState(state);
2274            }
2275            if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
2276                dr.mDrawableBottom.setState(state);
2277            }
2278            if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
2279                dr.mDrawableLeft.setState(state);
2280            }
2281            if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
2282                dr.mDrawableRight.setState(state);
2283            }
2284        }
2285    }
2286
2287    /**
2288     * User interface state that is stored by TextView for implementing
2289     * {@link View#onSaveInstanceState}.
2290     */
2291    public static class SavedState extends BaseSavedState {
2292        int selStart;
2293        int selEnd;
2294        CharSequence text;
2295        boolean frozenWithFocus;
2296        CharSequence error;
2297
2298        SavedState(Parcelable superState) {
2299            super(superState);
2300        }
2301
2302        @Override
2303        public void writeToParcel(Parcel out, int flags) {
2304            super.writeToParcel(out, flags);
2305            out.writeInt(selStart);
2306            out.writeInt(selEnd);
2307            out.writeInt(frozenWithFocus ? 1 : 0);
2308            TextUtils.writeToParcel(text, out, flags);
2309
2310            if (error == null) {
2311                out.writeInt(0);
2312            } else {
2313                out.writeInt(1);
2314                TextUtils.writeToParcel(error, out, flags);
2315            }
2316        }
2317
2318        @Override
2319        public String toString() {
2320            String str = "TextView.SavedState{"
2321                    + Integer.toHexString(System.identityHashCode(this))
2322                    + " start=" + selStart + " end=" + selEnd;
2323            if (text != null) {
2324                str += " text=" + text;
2325            }
2326            return str + "}";
2327        }
2328
2329        public static final Parcelable.Creator<SavedState> CREATOR
2330                = new Parcelable.Creator<SavedState>() {
2331            public SavedState createFromParcel(Parcel in) {
2332                return new SavedState(in);
2333            }
2334
2335            public SavedState[] newArray(int size) {
2336                return new SavedState[size];
2337            }
2338        };
2339
2340        private SavedState(Parcel in) {
2341            super(in);
2342            selStart = in.readInt();
2343            selEnd = in.readInt();
2344            frozenWithFocus = (in.readInt() != 0);
2345            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2346
2347            if (in.readInt() != 0) {
2348                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2349            }
2350        }
2351    }
2352
2353    @Override
2354    public Parcelable onSaveInstanceState() {
2355        Parcelable superState = super.onSaveInstanceState();
2356
2357        // Save state if we are forced to
2358        boolean save = mFreezesText;
2359        int start = 0;
2360        int end = 0;
2361
2362        if (mText != null) {
2363            start = Selection.getSelectionStart(mText);
2364            end = Selection.getSelectionEnd(mText);
2365            if (start >= 0 || end >= 0) {
2366                // Or save state if there is a selection
2367                save = true;
2368            }
2369        }
2370
2371        if (save) {
2372            SavedState ss = new SavedState(superState);
2373            // XXX Should also save the current scroll position!
2374            ss.selStart = start;
2375            ss.selEnd = end;
2376
2377            if (mText instanceof Spanned) {
2378                /*
2379                 * Calling setText() strips off any ChangeWatchers;
2380                 * strip them now to avoid leaking references.
2381                 * But do it to a copy so that if there are any
2382                 * further changes to the text of this view, it
2383                 * won't get into an inconsistent state.
2384                 */
2385
2386                Spannable sp = new SpannableString(mText);
2387
2388                for (ChangeWatcher cw :
2389                     sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2390                    sp.removeSpan(cw);
2391                }
2392
2393                ss.text = sp;
2394            } else {
2395                ss.text = mText.toString();
2396            }
2397
2398            if (isFocused() && start >= 0 && end >= 0) {
2399                ss.frozenWithFocus = true;
2400            }
2401
2402            ss.error = mError;
2403
2404            return ss;
2405        }
2406
2407        return superState;
2408    }
2409
2410    @Override
2411    public void onRestoreInstanceState(Parcelable state) {
2412        if (!(state instanceof SavedState)) {
2413            super.onRestoreInstanceState(state);
2414            return;
2415        }
2416
2417        SavedState ss = (SavedState)state;
2418        super.onRestoreInstanceState(ss.getSuperState());
2419
2420        // XXX restore buffer type too, as well as lots of other stuff
2421        if (ss.text != null) {
2422            setText(ss.text);
2423        }
2424
2425        if (ss.selStart >= 0 && ss.selEnd >= 0) {
2426            if (mText instanceof Spannable) {
2427                int len = mText.length();
2428
2429                if (ss.selStart > len || ss.selEnd > len) {
2430                    String restored = "";
2431
2432                    if (ss.text != null) {
2433                        restored = "(restored) ";
2434                    }
2435
2436                    Log.e("TextView", "Saved cursor position " + ss.selStart +
2437                          "/" + ss.selEnd + " out of range for " + restored +
2438                          "text " + mText);
2439                } else {
2440                    Selection.setSelection((Spannable) mText, ss.selStart,
2441                                           ss.selEnd);
2442
2443                    if (ss.frozenWithFocus) {
2444                        mFrozenWithFocus = true;
2445                    }
2446                }
2447            }
2448        }
2449
2450        if (ss.error != null) {
2451            final CharSequence error = ss.error;
2452            // Display the error later, after the first layout pass
2453            post(new Runnable() {
2454                public void run() {
2455                    setError(error);
2456                }
2457            });
2458        }
2459    }
2460
2461    /**
2462     * Control whether this text view saves its entire text contents when
2463     * freezing to an icicle, in addition to dynamic state such as cursor
2464     * position.  By default this is false, not saving the text.  Set to true
2465     * if the text in the text view is not being saved somewhere else in
2466     * persistent storage (such as in a content provider) so that if the
2467     * view is later thawed the user will not lose their data.
2468     *
2469     * @param freezesText Controls whether a frozen icicle should include the
2470     * entire text data: true to include it, false to not.
2471     *
2472     * @attr ref android.R.styleable#TextView_freezesText
2473     */
2474    @android.view.RemotableViewMethod
2475    public void setFreezesText(boolean freezesText) {
2476        mFreezesText = freezesText;
2477    }
2478
2479    /**
2480     * Return whether this text view is including its entire text contents
2481     * in frozen icicles.
2482     *
2483     * @return Returns true if text is included, false if it isn't.
2484     *
2485     * @see #setFreezesText
2486     */
2487    public boolean getFreezesText() {
2488        return mFreezesText;
2489    }
2490
2491    ///////////////////////////////////////////////////////////////////////////
2492
2493    /**
2494     * Sets the Factory used to create new Editables.
2495     */
2496    public final void setEditableFactory(Editable.Factory factory) {
2497        mEditableFactory = factory;
2498        setText(mText);
2499    }
2500
2501    /**
2502     * Sets the Factory used to create new Spannables.
2503     */
2504    public final void setSpannableFactory(Spannable.Factory factory) {
2505        mSpannableFactory = factory;
2506        setText(mText);
2507    }
2508
2509    /**
2510     * Sets the string value of the TextView. TextView <em>does not</em> accept
2511     * HTML-like formatting, which you can do with text strings in XML resource files.
2512     * To style your strings, attach android.text.style.* objects to a
2513     * {@link android.text.SpannableString SpannableString}, or see the
2514     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
2515     * Available Resource Types</a> documentation for an example of setting
2516     * formatted text in the XML resource file.
2517     *
2518     * @attr ref android.R.styleable#TextView_text
2519     */
2520    @android.view.RemotableViewMethod
2521    public final void setText(CharSequence text) {
2522        setText(text, mBufferType);
2523    }
2524
2525    /**
2526     * Like {@link #setText(CharSequence)},
2527     * except that the cursor position (if any) is retained in the new text.
2528     *
2529     * @param text The new text to place in the text view.
2530     *
2531     * @see #setText(CharSequence)
2532     */
2533    @android.view.RemotableViewMethod
2534    public final void setTextKeepState(CharSequence text) {
2535        setTextKeepState(text, mBufferType);
2536    }
2537
2538    /**
2539     * Sets the text that this TextView is to display (see
2540     * {@link #setText(CharSequence)}) and also sets whether it is stored
2541     * in a styleable/spannable buffer and whether it is editable.
2542     *
2543     * @attr ref android.R.styleable#TextView_text
2544     * @attr ref android.R.styleable#TextView_bufferType
2545     */
2546    public void setText(CharSequence text, BufferType type) {
2547        setText(text, type, true, 0);
2548
2549        if (mCharWrapper != null) {
2550            mCharWrapper.mChars = null;
2551        }
2552    }
2553
2554    private void setText(CharSequence text, BufferType type,
2555                         boolean notifyBefore, int oldlen) {
2556        if (text == null) {
2557            text = "";
2558        }
2559
2560        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
2561
2562        if (text instanceof Spanned &&
2563            ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
2564            setHorizontalFadingEdgeEnabled(true);
2565            setEllipsize(TextUtils.TruncateAt.MARQUEE);
2566        }
2567
2568        int n = mFilters.length;
2569        for (int i = 0; i < n; i++) {
2570            CharSequence out = mFilters[i].filter(text, 0, text.length(),
2571                                                  EMPTY_SPANNED, 0, 0);
2572            if (out != null) {
2573                text = out;
2574            }
2575        }
2576
2577        if (notifyBefore) {
2578            if (mText != null) {
2579                oldlen = mText.length();
2580                sendBeforeTextChanged(mText, 0, oldlen, text.length());
2581            } else {
2582                sendBeforeTextChanged("", 0, 0, text.length());
2583            }
2584        }
2585
2586        boolean needEditableForNotification = false;
2587
2588        if (mListeners != null && mListeners.size() != 0) {
2589            needEditableForNotification = true;
2590        }
2591
2592        if (type == BufferType.EDITABLE || mInput != null ||
2593            needEditableForNotification) {
2594            Editable t = mEditableFactory.newEditable(text);
2595            text = t;
2596            setFilters(t, mFilters);
2597            InputMethodManager imm = InputMethodManager.peekInstance();
2598            if (imm != null) imm.restartInput(this);
2599        } else if (type == BufferType.SPANNABLE || mMovement != null) {
2600            text = mSpannableFactory.newSpannable(text);
2601        } else if (!(text instanceof CharWrapper)) {
2602            text = TextUtils.stringOrSpannedString(text);
2603        }
2604
2605        if (mAutoLinkMask != 0) {
2606            Spannable s2;
2607
2608            if (type == BufferType.EDITABLE || text instanceof Spannable) {
2609                s2 = (Spannable) text;
2610            } else {
2611                s2 = mSpannableFactory.newSpannable(text);
2612            }
2613
2614            if (Linkify.addLinks(s2, mAutoLinkMask)) {
2615                text = s2;
2616                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
2617
2618                /*
2619                 * We must go ahead and set the text before changing the
2620                 * movement method, because setMovementMethod() may call
2621                 * setText() again to try to upgrade the buffer type.
2622                 */
2623                mText = text;
2624
2625                if (mLinksClickable) {
2626                    setMovementMethod(LinkMovementMethod.getInstance());
2627                }
2628            }
2629        }
2630
2631        mBufferType = type;
2632        mText = text;
2633
2634        if (mTransformation == null)
2635            mTransformed = text;
2636        else
2637            mTransformed = mTransformation.getTransformation(text, this);
2638
2639        final int textLength = text.length();
2640
2641        if (text instanceof Spannable) {
2642            Spannable sp = (Spannable) text;
2643
2644            // Remove any ChangeWatchers that might have come
2645            // from other TextViews.
2646            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
2647            final int count = watchers.length;
2648            for (int i = 0; i < count; i++)
2649                sp.removeSpan(watchers[i]);
2650
2651            if (mChangeWatcher == null)
2652                mChangeWatcher = new ChangeWatcher();
2653
2654            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
2655                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
2656
2657            if (mInput != null) {
2658                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2659            }
2660
2661            if (mTransformation != null) {
2662                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2663
2664            }
2665
2666            if (mMovement != null) {
2667                mMovement.initialize(this, (Spannable) text);
2668
2669                /*
2670                 * Initializing the movement method will have set the
2671                 * selection, so reset mSelectionMoved to keep that from
2672                 * interfering with the normal on-focus selection-setting.
2673                 */
2674                mSelectionMoved = false;
2675            }
2676        }
2677
2678        if (mLayout != null) {
2679            checkForRelayout();
2680        }
2681
2682        sendOnTextChanged(text, 0, oldlen, textLength);
2683        onTextChanged(text, 0, oldlen, textLength);
2684
2685        if (needEditableForNotification) {
2686            sendAfterTextChanged((Editable) text);
2687        }
2688    }
2689
2690    /**
2691     * Sets the TextView to display the specified slice of the specified
2692     * char array.  You must promise that you will not change the contents
2693     * of the array except for right before another call to setText(),
2694     * since the TextView has no way to know that the text
2695     * has changed and that it needs to invalidate and re-layout.
2696     */
2697    public final void setText(char[] text, int start, int len) {
2698        int oldlen = 0;
2699
2700        if (start < 0 || len < 0 || start + len > text.length) {
2701            throw new IndexOutOfBoundsException(start + ", " + len);
2702        }
2703
2704        /*
2705         * We must do the before-notification here ourselves because if
2706         * the old text is a CharWrapper we destroy it before calling
2707         * into the normal path.
2708         */
2709        if (mText != null) {
2710            oldlen = mText.length();
2711            sendBeforeTextChanged(mText, 0, oldlen, len);
2712        } else {
2713            sendBeforeTextChanged("", 0, 0, len);
2714        }
2715
2716        if (mCharWrapper == null) {
2717            mCharWrapper = new CharWrapper(text, start, len);
2718        } else {
2719            mCharWrapper.set(text, start, len);
2720        }
2721
2722        setText(mCharWrapper, mBufferType, false, oldlen);
2723    }
2724
2725    private static class CharWrapper
2726            implements CharSequence, GetChars, GraphicsOperations {
2727        private char[] mChars;
2728        private int mStart, mLength;
2729
2730        public CharWrapper(char[] chars, int start, int len) {
2731            mChars = chars;
2732            mStart = start;
2733            mLength = len;
2734        }
2735
2736        /* package */ void set(char[] chars, int start, int len) {
2737            mChars = chars;
2738            mStart = start;
2739            mLength = len;
2740        }
2741
2742        public int length() {
2743            return mLength;
2744        }
2745
2746        public char charAt(int off) {
2747            return mChars[off + mStart];
2748        }
2749
2750        public String toString() {
2751            return new String(mChars, mStart, mLength);
2752        }
2753
2754        public CharSequence subSequence(int start, int end) {
2755            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2756                throw new IndexOutOfBoundsException(start + ", " + end);
2757            }
2758
2759            return new String(mChars, start + mStart, end - start);
2760        }
2761
2762        public void getChars(int start, int end, char[] buf, int off) {
2763            if (start < 0 || end < 0 || start > mLength || end > mLength) {
2764                throw new IndexOutOfBoundsException(start + ", " + end);
2765            }
2766
2767            System.arraycopy(mChars, start + mStart, buf, off, end - start);
2768        }
2769
2770        public void drawText(Canvas c, int start, int end,
2771                             float x, float y, Paint p) {
2772            c.drawText(mChars, start + mStart, end - start, x, y, p);
2773        }
2774
2775        public float measureText(int start, int end, Paint p) {
2776            return p.measureText(mChars, start + mStart, end - start);
2777        }
2778
2779        public int getTextWidths(int start, int end, float[] widths, Paint p) {
2780            return p.getTextWidths(mChars, start + mStart, end - start, widths);
2781        }
2782    }
2783
2784    /**
2785     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2786     * except that the cursor position (if any) is retained in the new text.
2787     *
2788     * @see #setText(CharSequence, android.widget.TextView.BufferType)
2789     */
2790    public final void setTextKeepState(CharSequence text, BufferType type) {
2791        int start = getSelectionStart();
2792        int end = getSelectionEnd();
2793        int len = text.length();
2794
2795        setText(text, type);
2796
2797        if (start >= 0 || end >= 0) {
2798            if (mText instanceof Spannable) {
2799                Selection.setSelection((Spannable) mText,
2800                                       Math.max(0, Math.min(start, len)),
2801                                       Math.max(0, Math.min(end, len)));
2802            }
2803        }
2804    }
2805
2806    @android.view.RemotableViewMethod
2807    public final void setText(int resid) {
2808        setText(getContext().getResources().getText(resid));
2809    }
2810
2811    public final void setText(int resid, BufferType type) {
2812        setText(getContext().getResources().getText(resid), type);
2813    }
2814
2815    /**
2816     * Sets the text to be displayed when the text of the TextView is empty.
2817     * Null means to use the normal empty text. The hint does not currently
2818     * participate in determining the size of the view.
2819     *
2820     * This method is deprecated. Use {link #setHint(int, String)} or
2821     * {link #setHint(CharSequence, String)} instead.
2822     *
2823     * @attr ref android.R.styleable#TextView_hint
2824     */
2825    @android.view.RemotableViewMethod
2826    public final void setHint(CharSequence hint) {
2827        mHint = TextUtils.stringOrSpannedString(hint);
2828
2829        if (mLayout != null) {
2830            checkForRelayout();
2831        }
2832
2833        if (mText.length() == 0) {
2834            invalidate();
2835        }
2836    }
2837
2838    /**
2839     * Sets the text to be displayed when the text of the TextView is empty,
2840     * from a resource.
2841     *
2842     * This method is deprecated. Use {link #setHint(int, String)} or
2843     * {link #setHint(CharSequence, String)} instead.
2844     *
2845     * @attr ref android.R.styleable#TextView_hint
2846     */
2847    @android.view.RemotableViewMethod
2848    public final void setHint(int resid) {
2849        setHint(getContext().getResources().getText(resid));
2850    }
2851
2852    /**
2853     * Returns the hint that is displayed when the text of the TextView
2854     * is empty.
2855     *
2856     * @attr ref android.R.styleable#TextView_hint
2857     */
2858    @ViewDebug.CapturedViewProperty
2859    public CharSequence getHint() {
2860        return mHint;
2861    }
2862
2863    /**
2864     * Set the type of the content with a constant as defined for
2865     * {@link EditorInfo#inputType}.  This will take care of changing
2866     * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
2867     * match the given content type.  If the given content type is
2868     * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
2869     * not be displayed for this text view.
2870     *
2871     * @see #getInputType()
2872     * @see #setRawInputType(int)
2873     * @see android.text.InputType
2874     * @attr ref android.R.styleable#TextView_inputType
2875     */
2876    public void setInputType(int type) {
2877        final boolean wasPassword = isPasswordInputType(mInputType);
2878        final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
2879        setInputType(type, false);
2880        final boolean isPassword = isPasswordInputType(type);
2881        final boolean isVisiblePassword = isVisiblePasswordInputType(type);
2882        boolean forceUpdate = false;
2883        if (isPassword) {
2884            setTransformationMethod(PasswordTransformationMethod.getInstance());
2885            setTypefaceByIndex(MONOSPACE, 0);
2886        } else if (isVisiblePassword) {
2887            setTypefaceByIndex(MONOSPACE, 0);
2888        } else if (wasPassword || wasVisiblePassword) {
2889            // not in password mode, clean up typeface and transformation
2890            setTypefaceByIndex(-1, -1);
2891            if (mTransformation == PasswordTransformationMethod.getInstance()) {
2892                forceUpdate = true;
2893            }
2894        }
2895
2896        boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
2897                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
2898                (EditorInfo.TYPE_CLASS_TEXT
2899                        | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
2900
2901        // We need to update the single line mode if it has changed or we
2902        // were previously in password mode.
2903        if (mSingleLine == multiLine || forceUpdate) {
2904            // Change single line mode, but only change the transformation if
2905            // we are not in password mode.
2906            applySingleLine(!multiLine, !isPassword);
2907        }
2908
2909        InputMethodManager imm = InputMethodManager.peekInstance();
2910        if (imm != null) imm.restartInput(this);
2911    }
2912
2913    private boolean isPasswordInputType(int inputType) {
2914        final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
2915                | EditorInfo.TYPE_MASK_VARIATION);
2916        return variation
2917                == (EditorInfo.TYPE_CLASS_TEXT
2918                        | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
2919    }
2920
2921    private boolean isVisiblePasswordInputType(int inputType) {
2922        final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
2923                | EditorInfo.TYPE_MASK_VARIATION);
2924        return variation
2925                == (EditorInfo.TYPE_CLASS_TEXT
2926                        | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
2927    }
2928
2929    /**
2930     * Directly change the content type integer of the text view, without
2931     * modifying any other state.
2932     * @see #setInputType(int)
2933     * @see android.text.InputType
2934     * @attr ref android.R.styleable#TextView_inputType
2935     */
2936    public void setRawInputType(int type) {
2937        mInputType = type;
2938    }
2939
2940    private void setInputType(int type, boolean direct) {
2941        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
2942        KeyListener input;
2943        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
2944            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
2945                    != 0;
2946            TextKeyListener.Capitalize cap;
2947            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
2948                cap = TextKeyListener.Capitalize.CHARACTERS;
2949            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
2950                cap = TextKeyListener.Capitalize.WORDS;
2951            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
2952                cap = TextKeyListener.Capitalize.SENTENCES;
2953            } else {
2954                cap = TextKeyListener.Capitalize.NONE;
2955            }
2956            input = TextKeyListener.getInstance(autotext, cap);
2957        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
2958            input = DigitsKeyListener.getInstance(
2959                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
2960                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
2961        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
2962            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
2963                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
2964                    input = DateKeyListener.getInstance();
2965                    break;
2966                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
2967                    input = TimeKeyListener.getInstance();
2968                    break;
2969                default:
2970                    input = DateTimeKeyListener.getInstance();
2971                    break;
2972            }
2973        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
2974            input = DialerKeyListener.getInstance();
2975        } else {
2976            input = TextKeyListener.getInstance();
2977        }
2978        mInputType = type;
2979        if (direct) mInput = input;
2980        else {
2981            setKeyListenerOnly(input);
2982        }
2983    }
2984
2985    /**
2986     * Get the type of the content.
2987     *
2988     * @see #setInputType(int)
2989     * @see android.text.InputType
2990     */
2991    public int getInputType() {
2992        return mInputType;
2993    }
2994
2995    /**
2996     * Change the editor type integer associated with the text view, which
2997     * will be reported to an IME with {@link EditorInfo#imeOptions} when it
2998     * has focus.
2999     * @see #getImeOptions
3000     * @see android.view.inputmethod.EditorInfo
3001     * @attr ref android.R.styleable#TextView_imeOptions
3002     */
3003    public void setImeOptions(int imeOptions) {
3004        if (mInputContentType == null) {
3005            mInputContentType = new InputContentType();
3006        }
3007        mInputContentType.imeOptions = imeOptions;
3008    }
3009
3010    /**
3011     * Get the type of the IME editor.
3012     *
3013     * @see #setImeOptions(int)
3014     * @see android.view.inputmethod.EditorInfo
3015     */
3016    public int getImeOptions() {
3017        return mInputContentType != null
3018                ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
3019    }
3020
3021    /**
3022     * Change the custom IME action associated with the text view, which
3023     * will be reported to an IME with {@link EditorInfo#actionLabel}
3024     * and {@link EditorInfo#actionId} when it has focus.
3025     * @see #getImeActionLabel
3026     * @see #getImeActionId
3027     * @see android.view.inputmethod.EditorInfo
3028     * @attr ref android.R.styleable#TextView_imeActionLabel
3029     * @attr ref android.R.styleable#TextView_imeActionId
3030     */
3031    public void setImeActionLabel(CharSequence label, int actionId) {
3032        if (mInputContentType == null) {
3033            mInputContentType = new InputContentType();
3034        }
3035        mInputContentType.imeActionLabel = label;
3036        mInputContentType.imeActionId = actionId;
3037    }
3038
3039    /**
3040     * Get the IME action label previous set with {@link #setImeActionLabel}.
3041     *
3042     * @see #setImeActionLabel
3043     * @see android.view.inputmethod.EditorInfo
3044     */
3045    public CharSequence getImeActionLabel() {
3046        return mInputContentType != null
3047                ? mInputContentType.imeActionLabel : null;
3048    }
3049
3050    /**
3051     * Get the IME action ID previous set with {@link #setImeActionLabel}.
3052     *
3053     * @see #setImeActionLabel
3054     * @see android.view.inputmethod.EditorInfo
3055     */
3056    public int getImeActionId() {
3057        return mInputContentType != null
3058                ? mInputContentType.imeActionId : 0;
3059    }
3060
3061    /**
3062     * Set a special listener to be called when an action is performed
3063     * on the text view.  This will be called when the enter key is pressed,
3064     * or when an action supplied to the IME is selected by the user.  Setting
3065     * this means that the normal hard key event will not insert a newline
3066     * into the text view, even if it is multi-line; holding down the ALT
3067     * modifier will, however, allow the user to insert a newline character.
3068     */
3069    public void setOnEditorActionListener(OnEditorActionListener l) {
3070        if (mInputContentType == null) {
3071            mInputContentType = new InputContentType();
3072        }
3073        mInputContentType.onEditorActionListener = l;
3074    }
3075
3076    /**
3077     * Called when an attached input method calls
3078     * {@link InputConnection#performEditorAction(int)
3079     * InputConnection.performEditorAction()}
3080     * for this text view.  The default implementation will call your action
3081     * listener supplied to {@link #setOnEditorActionListener}, or perform
3082     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
3083     * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
3084     * EditorInfo.IME_ACTION_DONE}.
3085     *
3086     * <p>For backwards compatibility, if no IME options have been set and the
3087     * text view would not normally advance focus on enter, then
3088     * the NEXT and DONE actions received here will be turned into an enter
3089     * key down/up pair to go through the normal key handling.
3090     *
3091     * @param actionCode The code of the action being performed.
3092     *
3093     * @see #setOnEditorActionListener
3094     */
3095    public void onEditorAction(int actionCode) {
3096        final InputContentType ict = mInputContentType;
3097        if (ict != null) {
3098            if (ict.onEditorActionListener != null) {
3099                if (ict.onEditorActionListener.onEditorAction(this,
3100                        actionCode, null)) {
3101                    return;
3102                }
3103            }
3104
3105            // This is the handling for some default action.
3106            // Note that for backwards compatibility we don't do this
3107            // default handling if explicit ime options have not been given,
3108            // instead turning this into the normal enter key codes that an
3109            // app may be expecting.
3110            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3111                View v = focusSearch(FOCUS_DOWN);
3112                if (v != null) {
3113                    if (!v.requestFocus(FOCUS_DOWN)) {
3114                        throw new IllegalStateException("focus search returned a view " +
3115                                "that wasn't able to take focus!");
3116                    }
3117                }
3118                return;
3119
3120            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3121                InputMethodManager imm = InputMethodManager.peekInstance();
3122                if (imm != null) {
3123                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
3124                }
3125                return;
3126            }
3127        }
3128
3129        Handler h = getHandler();
3130        if (h != null) {
3131            long eventTime = SystemClock.uptimeMillis();
3132            h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3133                    new KeyEvent(eventTime, eventTime,
3134                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3135                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3136                    | KeyEvent.FLAG_EDITOR_ACTION)));
3137            h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3138                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
3139                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3140                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3141                    | KeyEvent.FLAG_EDITOR_ACTION)));
3142        }
3143    }
3144
3145    /**
3146     * Set the private content type of the text, which is the
3147     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3148     * field that will be filled in when creating an input connection.
3149     *
3150     * @see #getPrivateImeOptions()
3151     * @see EditorInfo#privateImeOptions
3152     * @attr ref android.R.styleable#TextView_privateImeOptions
3153     */
3154    public void setPrivateImeOptions(String type) {
3155        if (mInputContentType == null) mInputContentType = new InputContentType();
3156        mInputContentType.privateImeOptions = type;
3157    }
3158
3159    /**
3160     * Get the private type of the content.
3161     *
3162     * @see #setPrivateImeOptions(String)
3163     * @see EditorInfo#privateImeOptions
3164     */
3165    public String getPrivateImeOptions() {
3166        return mInputContentType != null
3167                ? mInputContentType.privateImeOptions : null;
3168    }
3169
3170    /**
3171     * Set the extra input data of the text, which is the
3172     * {@link EditorInfo#extras TextBoxAttribute.extras}
3173     * Bundle that will be filled in when creating an input connection.  The
3174     * given integer is the resource ID of an XML resource holding an
3175     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3176     *
3177     * @see #getInputExtras(boolean)
3178     * @see EditorInfo#extras
3179     * @attr ref android.R.styleable#TextView_editorExtras
3180     */
3181    public void setInputExtras(int xmlResId)
3182            throws XmlPullParserException, IOException {
3183        XmlResourceParser parser = getResources().getXml(xmlResId);
3184        if (mInputContentType == null) mInputContentType = new InputContentType();
3185        mInputContentType.extras = new Bundle();
3186        getResources().parseBundleExtras(parser, mInputContentType.extras);
3187    }
3188
3189    /**
3190     * Retrieve the input extras currently associated with the text view, which
3191     * can be viewed as well as modified.
3192     *
3193     * @param create If true, the extras will be created if they don't already
3194     * exist.  Otherwise, null will be returned if none have been created.
3195     * @see #setInputExtras(int)View
3196     * @see EditorInfo#extras
3197     * @attr ref android.R.styleable#TextView_editorExtras
3198     */
3199    public Bundle getInputExtras(boolean create) {
3200        if (mInputContentType == null) {
3201            if (!create) return null;
3202            mInputContentType = new InputContentType();
3203        }
3204        if (mInputContentType.extras == null) {
3205            if (!create) return null;
3206            mInputContentType.extras = new Bundle();
3207        }
3208        return mInputContentType.extras;
3209    }
3210
3211    /**
3212     * Returns the error message that was set to be displayed with
3213     * {@link #setError}, or <code>null</code> if no error was set
3214     * or if it the error was cleared by the widget after user input.
3215     */
3216    public CharSequence getError() {
3217        return mError;
3218    }
3219
3220    /**
3221     * Sets the right-hand compound drawable of the TextView to the "error"
3222     * icon and sets an error message that will be displayed in a popup when
3223     * the TextView has focus.  The icon and error message will be reset to
3224     * null when any key events cause changes to the TextView's text.  If the
3225     * <code>error</code> is <code>null</code>, the error message and icon
3226     * will be cleared.
3227     */
3228    @android.view.RemotableViewMethod
3229    public void setError(CharSequence error) {
3230        if (error == null) {
3231            setError(null, null);
3232        } else {
3233            Drawable dr = getContext().getResources().
3234                getDrawable(com.android.internal.R.drawable.
3235                            indicator_input_error);
3236
3237            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3238            setError(error, dr);
3239        }
3240    }
3241
3242    /**
3243     * Sets the right-hand compound drawable of the TextView to the specified
3244     * icon and sets an error message that will be displayed in a popup when
3245     * the TextView has focus.  The icon and error message will be reset to
3246     * null when any key events cause changes to the TextView's text.  The
3247     * drawable must already have had {@link Drawable#setBounds} set on it.
3248     * If the <code>error</code> is <code>null</code>, the error message will
3249     * be cleared (and you should provide a <code>null</code> icon as well).
3250     */
3251    public void setError(CharSequence error, Drawable icon) {
3252        error = TextUtils.stringOrSpannedString(error);
3253
3254        mError = error;
3255        mErrorWasChanged = true;
3256        final Drawables dr = mDrawables;
3257        if (dr != null) {
3258            setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
3259                                 icon, dr.mDrawableBottom);
3260        } else {
3261            setCompoundDrawables(null, null, icon, null);
3262        }
3263
3264        if (error == null) {
3265            if (mPopup != null) {
3266                if (mPopup.isShowing()) {
3267                    mPopup.dismiss();
3268                }
3269
3270                mPopup = null;
3271            }
3272        } else {
3273            if (isFocused()) {
3274                showError();
3275            }
3276        }
3277    }
3278
3279    private void showError() {
3280        if (getWindowToken() == null) {
3281            mShowErrorAfterAttach = true;
3282            return;
3283        }
3284
3285        if (mPopup == null) {
3286            LayoutInflater inflater = LayoutInflater.from(getContext());
3287            final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
3288                    null);
3289
3290            final float scale = getResources().getDisplayMetrics().density;
3291            mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f),
3292                    (int) (50 * scale + 0.5f));
3293            mPopup.setFocusable(false);
3294            // The user is entering text, so the input method is needed.  We
3295            // don't want the popup to be displayed on top of it.
3296            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
3297        }
3298
3299        TextView tv = (TextView) mPopup.getContentView();
3300        chooseSize(mPopup, mError, tv);
3301        tv.setText(mError);
3302
3303        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
3304        mPopup.fixDirection(mPopup.isAboveAnchor());
3305    }
3306
3307    private static class ErrorPopup extends PopupWindow {
3308        private boolean mAbove = false;
3309        private TextView mView;
3310
3311        ErrorPopup(TextView v, int width, int height) {
3312            super(v, width, height);
3313            mView = v;
3314        }
3315
3316        void fixDirection(boolean above) {
3317            mAbove = above;
3318
3319            if (above) {
3320                mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
3321            } else {
3322                mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
3323            }
3324        }
3325
3326        @Override
3327        public void update(int x, int y, int w, int h, boolean force) {
3328            super.update(x, y, w, h, force);
3329
3330            boolean above = isAboveAnchor();
3331            if (above != mAbove) {
3332                fixDirection(above);
3333            }
3334        }
3335    }
3336
3337    /**
3338     * Returns the Y offset to make the pointy top of the error point
3339     * at the middle of the error icon.
3340     */
3341    private int getErrorX() {
3342        /*
3343         * The "25" is the distance between the point and the right edge
3344         * of the background
3345         */
3346        final float scale = getResources().getDisplayMetrics().density;
3347
3348        final Drawables dr = mDrawables;
3349        return getWidth() - mPopup.getWidth()
3350                - getPaddingRight()
3351                - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
3352    }
3353
3354    /**
3355     * Returns the Y offset to make the pointy top of the error point
3356     * at the bottom of the error icon.
3357     */
3358    private int getErrorY() {
3359        /*
3360         * Compound, not extended, because the icon is not clipped
3361         * if the text height is smaller.
3362         */
3363        int vspace = mBottom - mTop -
3364                     getCompoundPaddingBottom() - getCompoundPaddingTop();
3365
3366        final Drawables dr = mDrawables;
3367        int icontop = getCompoundPaddingTop()
3368                + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
3369
3370        /*
3371         * The "2" is the distance between the point and the top edge
3372         * of the background.
3373         */
3374
3375        return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
3376                - getHeight() - 2;
3377    }
3378
3379    private void hideError() {
3380        if (mPopup != null) {
3381            if (mPopup.isShowing()) {
3382                mPopup.dismiss();
3383            }
3384        }
3385
3386        mShowErrorAfterAttach = false;
3387    }
3388
3389    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
3390        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
3391        int ht = tv.getPaddingTop() + tv.getPaddingBottom();
3392
3393        /*
3394         * Figure out how big the text would be if we laid it out to the
3395         * full width of this view minus the border.
3396         */
3397        int cap = getWidth() - wid;
3398        if (cap < 0) {
3399            cap = 200; // We must not be measured yet -- setFrame() will fix it.
3400        }
3401
3402        Layout l = new StaticLayout(text, tv.getPaint(), cap,
3403                                    Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
3404        float max = 0;
3405        for (int i = 0; i < l.getLineCount(); i++) {
3406            max = Math.max(max, l.getLineWidth(i));
3407        }
3408
3409        /*
3410         * Now set the popup size to be big enough for the text plus the border.
3411         */
3412        pop.setWidth(wid + (int) Math.ceil(max));
3413        pop.setHeight(ht + l.getHeight());
3414    }
3415
3416
3417    @Override
3418    protected boolean setFrame(int l, int t, int r, int b) {
3419        boolean result = super.setFrame(l, t, r, b);
3420
3421        if (mPopup != null) {
3422            TextView tv = (TextView) mPopup.getContentView();
3423            chooseSize(mPopup, mError, tv);
3424            mPopup.update(this, getErrorX(), getErrorY(),
3425                          mPopup.getWidth(), mPopup.getHeight());
3426        }
3427
3428        restartMarqueeIfNeeded();
3429
3430        return result;
3431    }
3432
3433    private void restartMarqueeIfNeeded() {
3434        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3435            mRestartMarquee = false;
3436            startMarquee();
3437        }
3438    }
3439
3440    /**
3441     * Sets the list of input filters that will be used if the buffer is
3442     * Editable.  Has no effect otherwise.
3443     *
3444     * @attr ref android.R.styleable#TextView_maxLength
3445     */
3446    public void setFilters(InputFilter[] filters) {
3447        if (filters == null) {
3448            throw new IllegalArgumentException();
3449        }
3450
3451        mFilters = filters;
3452
3453        if (mText instanceof Editable) {
3454            setFilters((Editable) mText, filters);
3455        }
3456    }
3457
3458    /**
3459     * Sets the list of input filters on the specified Editable,
3460     * and includes mInput in the list if it is an InputFilter.
3461     */
3462    private void setFilters(Editable e, InputFilter[] filters) {
3463        if (mInput instanceof InputFilter) {
3464            InputFilter[] nf = new InputFilter[filters.length + 1];
3465
3466            System.arraycopy(filters, 0, nf, 0, filters.length);
3467            nf[filters.length] = (InputFilter) mInput;
3468
3469            e.setFilters(nf);
3470        } else {
3471            e.setFilters(filters);
3472        }
3473    }
3474
3475    /**
3476     * Returns the current list of input filters.
3477     */
3478    public InputFilter[] getFilters() {
3479        return mFilters;
3480    }
3481
3482    /////////////////////////////////////////////////////////////////////////
3483
3484    private int getVerticalOffset(boolean forceNormal) {
3485        int voffset = 0;
3486        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3487
3488        Layout l = mLayout;
3489        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3490            l = mHintLayout;
3491        }
3492
3493        if (gravity != Gravity.TOP) {
3494            int boxht;
3495
3496            if (l == mHintLayout) {
3497                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3498                        getCompoundPaddingBottom();
3499            } else {
3500                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3501                        getExtendedPaddingBottom();
3502            }
3503            int textht = l.getHeight();
3504
3505            if (textht < boxht) {
3506                if (gravity == Gravity.BOTTOM)
3507                    voffset = boxht - textht;
3508                else // (gravity == Gravity.CENTER_VERTICAL)
3509                    voffset = (boxht - textht) >> 1;
3510            }
3511        }
3512        return voffset;
3513    }
3514
3515    private int getBottomVerticalOffset(boolean forceNormal) {
3516        int voffset = 0;
3517        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3518
3519        Layout l = mLayout;
3520        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3521            l = mHintLayout;
3522        }
3523
3524        if (gravity != Gravity.BOTTOM) {
3525            int boxht;
3526
3527            if (l == mHintLayout) {
3528                boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3529                        getCompoundPaddingBottom();
3530            } else {
3531                boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3532                        getExtendedPaddingBottom();
3533            }
3534            int textht = l.getHeight();
3535
3536            if (textht < boxht) {
3537                if (gravity == Gravity.TOP)
3538                    voffset = boxht - textht;
3539                else // (gravity == Gravity.CENTER_VERTICAL)
3540                    voffset = (boxht - textht) >> 1;
3541            }
3542        }
3543        return voffset;
3544    }
3545
3546    private void invalidateCursorPath() {
3547        if (mHighlightPathBogus) {
3548            invalidateCursor();
3549        } else {
3550            synchronized (sTempRect) {
3551                /*
3552                 * The reason for this concern about the thickness of the
3553                 * cursor and doing the floor/ceil on the coordinates is that
3554                 * some EditTexts (notably textfields in the Browser) have
3555                 * anti-aliased text where not all the characters are
3556                 * necessarily at integer-multiple locations.  This should
3557                 * make sure the entire cursor gets invalidated instead of
3558                 * sometimes missing half a pixel.
3559                 */
3560
3561                float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
3562                if (thick < 1.0f) {
3563                    thick = 1.0f;
3564                }
3565
3566                thick /= 2;
3567
3568                mHighlightPath.computeBounds(sTempRect, false);
3569
3570                int left = getCompoundPaddingLeft();
3571                int top = getExtendedPaddingTop() + getVerticalOffset(true);
3572
3573                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
3574                           (int) FloatMath.floor(top + sTempRect.top - thick),
3575                           (int) FloatMath.ceil(left + sTempRect.right + thick),
3576                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
3577            }
3578        }
3579    }
3580
3581    private void invalidateCursor() {
3582        int where = Selection.getSelectionEnd(mText);
3583
3584        invalidateCursor(where, where, where);
3585    }
3586
3587    private void invalidateCursor(int a, int b, int c) {
3588        if (mLayout == null) {
3589            invalidate();
3590        } else {
3591            if (a >= 0 || b >= 0 || c >= 0) {
3592                int first = Math.min(Math.min(a, b), c);
3593                int last = Math.max(Math.max(a, b), c);
3594
3595                int line = mLayout.getLineForOffset(first);
3596                int top = mLayout.getLineTop(line);
3597
3598                // This is ridiculous, but the descent from the line above
3599                // can hang down into the line we really want to redraw,
3600                // so we have to invalidate part of the line above to make
3601                // sure everything that needs to be redrawn really is.
3602                // (But not the whole line above, because that would cause
3603                // the same problem with the descenders on the line above it!)
3604                if (line > 0) {
3605                    top -= mLayout.getLineDescent(line - 1);
3606                }
3607
3608                int line2;
3609
3610                if (first == last)
3611                    line2 = line;
3612                else
3613                    line2 = mLayout.getLineForOffset(last);
3614
3615                int bottom = mLayout.getLineTop(line2 + 1);
3616                int voffset = getVerticalOffset(true);
3617
3618                int left = getCompoundPaddingLeft() + mScrollX;
3619                invalidate(left, top + voffset + getExtendedPaddingTop(),
3620                           left + getWidth() - getCompoundPaddingLeft() -
3621                           getCompoundPaddingRight(),
3622                           bottom + voffset + getExtendedPaddingTop());
3623            }
3624        }
3625    }
3626
3627    private void registerForPreDraw() {
3628        final ViewTreeObserver observer = getViewTreeObserver();
3629        if (observer == null) {
3630            return;
3631        }
3632
3633        if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
3634            observer.addOnPreDrawListener(this);
3635            mPreDrawState = PREDRAW_PENDING;
3636        } else if (mPreDrawState == PREDRAW_DONE) {
3637            mPreDrawState = PREDRAW_PENDING;
3638        }
3639
3640        // else state is PREDRAW_PENDING, so keep waiting.
3641    }
3642
3643    /**
3644     * {@inheritDoc}
3645     */
3646    public boolean onPreDraw() {
3647        if (mPreDrawState != PREDRAW_PENDING) {
3648            return true;
3649        }
3650
3651        if (mLayout == null) {
3652            assumeLayout();
3653        }
3654
3655        boolean changed = false;
3656
3657        if (mMovement != null) {
3658            int curs = Selection.getSelectionEnd(mText);
3659
3660            /*
3661             * TODO: This should really only keep the end in view if
3662             * it already was before the text changed.  I'm not sure
3663             * of a good way to tell from here if it was.
3664             */
3665            if (curs < 0 &&
3666                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3667                curs = mText.length();
3668            }
3669
3670            if (curs >= 0) {
3671                changed = bringPointIntoView(curs);
3672            }
3673        } else {
3674            changed = bringTextIntoView();
3675        }
3676
3677        mPreDrawState = PREDRAW_DONE;
3678        return !changed;
3679    }
3680
3681    @Override
3682    protected void onAttachedToWindow() {
3683        super.onAttachedToWindow();
3684
3685        mTemporaryDetach = false;
3686
3687        if (mShowErrorAfterAttach) {
3688            showError();
3689            mShowErrorAfterAttach = false;
3690        }
3691    }
3692
3693    @Override
3694    protected void onDetachedFromWindow() {
3695        super.onDetachedFromWindow();
3696
3697        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
3698            final ViewTreeObserver observer = getViewTreeObserver();
3699            if (observer != null) {
3700                observer.removeOnPreDrawListener(this);
3701                mPreDrawState = PREDRAW_NOT_REGISTERED;
3702            }
3703        }
3704
3705        if (mError != null) {
3706            hideError();
3707        }
3708    }
3709
3710    @Override
3711    protected boolean isPaddingOffsetRequired() {
3712        return mShadowRadius != 0 || mDrawables != null;
3713    }
3714
3715    @Override
3716    protected int getLeftPaddingOffset() {
3717        return getCompoundPaddingLeft() - mPaddingLeft +
3718                (int) Math.min(0, mShadowDx - mShadowRadius);
3719    }
3720
3721    @Override
3722    protected int getTopPaddingOffset() {
3723        return (int) Math.min(0, mShadowDy - mShadowRadius);
3724    }
3725
3726    @Override
3727    protected int getBottomPaddingOffset() {
3728        return (int) Math.max(0, mShadowDy + mShadowRadius);
3729    }
3730
3731    @Override
3732    protected int getRightPaddingOffset() {
3733        return -(getCompoundPaddingRight() - mPaddingRight) +
3734                (int) Math.max(0, mShadowDx + mShadowRadius);
3735    }
3736
3737    @Override
3738    protected boolean verifyDrawable(Drawable who) {
3739        final boolean verified = super.verifyDrawable(who);
3740        if (!verified && mDrawables != null) {
3741            return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
3742                    who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
3743        }
3744        return verified;
3745    }
3746
3747    @Override
3748    public void invalidateDrawable(Drawable drawable) {
3749        if (verifyDrawable(drawable)) {
3750            final Rect dirty = drawable.getBounds();
3751            int scrollX = mScrollX;
3752            int scrollY = mScrollY;
3753
3754            // IMPORTANT: The coordinates below are based on the coordinates computed
3755            // for each compound drawable in onDraw(). Make sure to update each section
3756            // accordingly.
3757            final TextView.Drawables drawables = mDrawables;
3758            if (drawables != null) {
3759                if (drawable == drawables.mDrawableLeft) {
3760                    final int compoundPaddingTop = getCompoundPaddingTop();
3761                    final int compoundPaddingBottom = getCompoundPaddingBottom();
3762                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
3763
3764                    scrollX += mPaddingLeft;
3765                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
3766                } else if (drawable == drawables.mDrawableRight) {
3767                    final int compoundPaddingTop = getCompoundPaddingTop();
3768                    final int compoundPaddingBottom = getCompoundPaddingBottom();
3769                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
3770
3771                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
3772                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
3773                } else if (drawable == drawables.mDrawableTop) {
3774                    final int compoundPaddingLeft = getCompoundPaddingLeft();
3775                    final int compoundPaddingRight = getCompoundPaddingRight();
3776                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
3777
3778                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
3779                    scrollY += mPaddingTop;
3780                } else if (drawable == drawables.mDrawableBottom) {
3781                    final int compoundPaddingLeft = getCompoundPaddingLeft();
3782                    final int compoundPaddingRight = getCompoundPaddingRight();
3783                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
3784
3785                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
3786                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
3787                }
3788            }
3789
3790            invalidate(dirty.left + scrollX, dirty.top + scrollY,
3791                    dirty.right + scrollX, dirty.bottom + scrollY);
3792        }
3793    }
3794
3795    @Override
3796    protected void onDraw(Canvas canvas) {
3797        restartMarqueeIfNeeded();
3798
3799        // Draw the background for this view
3800        super.onDraw(canvas);
3801
3802        final int compoundPaddingLeft = getCompoundPaddingLeft();
3803        final int compoundPaddingTop = getCompoundPaddingTop();
3804        final int compoundPaddingRight = getCompoundPaddingRight();
3805        final int compoundPaddingBottom = getCompoundPaddingBottom();
3806        final int scrollX = mScrollX;
3807        final int scrollY = mScrollY;
3808        final int right = mRight;
3809        final int left = mLeft;
3810        final int bottom = mBottom;
3811        final int top = mTop;
3812
3813        final Drawables dr = mDrawables;
3814        if (dr != null) {
3815            /*
3816             * Compound, not extended, because the icon is not clipped
3817             * if the text height is smaller.
3818             */
3819
3820            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
3821            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
3822
3823            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3824            // Make sure to update invalidateDrawable() when changing this code.
3825            if (dr.mDrawableLeft != null) {
3826                canvas.save();
3827                canvas.translate(scrollX + mPaddingLeft,
3828                                 scrollY + compoundPaddingTop +
3829                                 (vspace - dr.mDrawableHeightLeft) / 2);
3830                dr.mDrawableLeft.draw(canvas);
3831                canvas.restore();
3832            }
3833
3834            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3835            // Make sure to update invalidateDrawable() when changing this code.
3836            if (dr.mDrawableRight != null) {
3837                canvas.save();
3838                canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
3839                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
3840                dr.mDrawableRight.draw(canvas);
3841                canvas.restore();
3842            }
3843
3844            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3845            // Make sure to update invalidateDrawable() when changing this code.
3846            if (dr.mDrawableTop != null) {
3847                canvas.save();
3848                canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
3849                        scrollY + mPaddingTop);
3850                dr.mDrawableTop.draw(canvas);
3851                canvas.restore();
3852            }
3853
3854            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3855            // Make sure to update invalidateDrawable() when changing this code.
3856            if (dr.mDrawableBottom != null) {
3857                canvas.save();
3858                canvas.translate(scrollX + compoundPaddingLeft +
3859                        (hspace - dr.mDrawableWidthBottom) / 2,
3860                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
3861                dr.mDrawableBottom.draw(canvas);
3862                canvas.restore();
3863            }
3864        }
3865
3866        if (mPreDrawState == PREDRAW_DONE) {
3867            final ViewTreeObserver observer = getViewTreeObserver();
3868            if (observer != null) {
3869                observer.removeOnPreDrawListener(this);
3870                mPreDrawState = PREDRAW_NOT_REGISTERED;
3871            }
3872        }
3873
3874        int color = mCurTextColor;
3875
3876        if (mLayout == null) {
3877            assumeLayout();
3878        }
3879
3880        Layout layout = mLayout;
3881        int cursorcolor = color;
3882
3883        if (mHint != null && mText.length() == 0) {
3884            if (mHintTextColor != null) {
3885                color = mCurHintTextColor;
3886            }
3887
3888            layout = mHintLayout;
3889        }
3890
3891        mTextPaint.setColor(color);
3892        mTextPaint.drawableState = getDrawableState();
3893
3894        canvas.save();
3895        /*  Would be faster if we didn't have to do this. Can we chop the
3896            (displayable) text so that we don't need to do this ever?
3897        */
3898
3899        int extendedPaddingTop = getExtendedPaddingTop();
3900        int extendedPaddingBottom = getExtendedPaddingBottom();
3901
3902        float clipLeft = compoundPaddingLeft + scrollX;
3903        float clipTop = extendedPaddingTop + scrollY;
3904        float clipRight = right - left - compoundPaddingRight + scrollX;
3905        float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
3906
3907        if (mShadowRadius != 0) {
3908            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
3909            clipRight += Math.max(0, mShadowDx + mShadowRadius);
3910
3911            clipTop += Math.min(0, mShadowDy - mShadowRadius);
3912            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
3913        }
3914
3915        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
3916
3917        int voffsetText = 0;
3918        int voffsetCursor = 0;
3919
3920        // translate in by our padding
3921        {
3922            /* shortcircuit calling getVerticaOffset() */
3923            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3924                voffsetText = getVerticalOffset(false);
3925                voffsetCursor = getVerticalOffset(true);
3926            }
3927            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
3928        }
3929
3930        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3931            if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
3932                    (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
3933                canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
3934                        getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
3935            }
3936
3937            if (mMarquee != null && mMarquee.isRunning()) {
3938                canvas.translate(-mMarquee.mScroll, 0.0f);
3939            }
3940        }
3941
3942        Path highlight = null;
3943        int selStart = -1, selEnd = -1;
3944
3945        //  If there is no movement method, then there can be no selection.
3946        //  Check that first and attempt to skip everything having to do with
3947        //  the cursor.
3948        //  XXX This is not strictly true -- a program could set the
3949        //  selection manually if it really wanted to.
3950        if (mMovement != null && (isFocused() || isPressed())) {
3951            selStart = Selection.getSelectionStart(mText);
3952            selEnd = Selection.getSelectionEnd(mText);
3953
3954            if (mCursorVisible && selStart >= 0 && isEnabled()) {
3955                if (mHighlightPath == null)
3956                    mHighlightPath = new Path();
3957
3958                if (selStart == selEnd) {
3959                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
3960                        < BLINK) {
3961                        if (mHighlightPathBogus) {
3962                            mHighlightPath.reset();
3963                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
3964                            mHighlightPathBogus = false;
3965                        }
3966
3967                        // XXX should pass to skin instead of drawing directly
3968                        mHighlightPaint.setColor(cursorcolor);
3969                        mHighlightPaint.setStyle(Paint.Style.STROKE);
3970
3971                        highlight = mHighlightPath;
3972                    }
3973                } else {
3974                    if (mHighlightPathBogus) {
3975                        mHighlightPath.reset();
3976                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
3977                        mHighlightPathBogus = false;
3978                    }
3979
3980                    // XXX should pass to skin instead of drawing directly
3981                    mHighlightPaint.setColor(mHighlightColor);
3982                    mHighlightPaint.setStyle(Paint.Style.FILL);
3983
3984                    highlight = mHighlightPath;
3985                }
3986            }
3987        }
3988
3989        /*  Comment out until we decide what to do about animations
3990        boolean isLinearTextOn = false;
3991        if (currentTransformation != null) {
3992            isLinearTextOn = mTextPaint.isLinearTextOn();
3993            Matrix m = currentTransformation.getMatrix();
3994            if (!m.isIdentity()) {
3995                // mTextPaint.setLinearTextOn(true);
3996            }
3997        }
3998        */
3999
4000        final InputMethodState ims = mInputMethodState;
4001        if (ims != null && ims.mBatchEditNesting == 0) {
4002            InputMethodManager imm = InputMethodManager.peekInstance();
4003            if (imm != null) {
4004                if (imm.isActive(this)) {
4005                    boolean reported = false;
4006                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
4007                        // We are in extract mode and the content has changed
4008                        // in some way... just report complete new text to the
4009                        // input method.
4010                        reported = reportExtractedText();
4011                    }
4012                    if (!reported && highlight != null) {
4013                        int candStart = -1;
4014                        int candEnd = -1;
4015                        if (mText instanceof Spannable) {
4016                            Spannable sp = (Spannable)mText;
4017                            candStart = EditableInputConnection.getComposingSpanStart(sp);
4018                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4019                        }
4020                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4021                    }
4022                }
4023
4024                if (imm.isWatchingCursor(this) && highlight != null) {
4025                    highlight.computeBounds(ims.mTmpRectF, true);
4026                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4027
4028                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
4029                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4030
4031                    ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
4032
4033                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4034                            (int)(ims.mTmpRectF.top + 0.5),
4035                            (int)(ims.mTmpRectF.right + 0.5),
4036                            (int)(ims.mTmpRectF.bottom + 0.5));
4037
4038                    imm.updateCursor(this,
4039                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4040                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4041                }
4042            }
4043        }
4044
4045        layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
4046
4047        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
4048            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
4049            layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
4050        }
4051
4052        /*  Comment out until we decide what to do about animations
4053        if (currentTransformation != null) {
4054            mTextPaint.setLinearTextOn(isLinearTextOn);
4055        }
4056        */
4057
4058        canvas.restore();
4059    }
4060
4061    @Override
4062    public void getFocusedRect(Rect r) {
4063        if (mLayout == null) {
4064            super.getFocusedRect(r);
4065            return;
4066        }
4067
4068        int sel = getSelectionEnd();
4069        if (sel < 0) {
4070            super.getFocusedRect(r);
4071            return;
4072        }
4073
4074        int line = mLayout.getLineForOffset(sel);
4075        r.top = mLayout.getLineTop(line);
4076        r.bottom = mLayout.getLineBottom(line);
4077
4078        r.left = (int) mLayout.getPrimaryHorizontal(sel);
4079        r.right = r.left + 1;
4080
4081        // Adjust for padding and gravity.
4082        int paddingLeft = getCompoundPaddingLeft();
4083        int paddingTop = getExtendedPaddingTop();
4084        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4085            paddingTop += getVerticalOffset(false);
4086        }
4087        r.offset(paddingLeft, paddingTop);
4088    }
4089
4090    /**
4091     * Return the number of lines of text, or 0 if the internal Layout has not
4092     * been built.
4093     */
4094    public int getLineCount() {
4095        return mLayout != null ? mLayout.getLineCount() : 0;
4096    }
4097
4098    /**
4099     * Return the baseline for the specified line (0...getLineCount() - 1)
4100     * If bounds is not null, return the top, left, right, bottom extents
4101     * of the specified line in it. If the internal Layout has not been built,
4102     * return 0 and set bounds to (0, 0, 0, 0)
4103     * @param line which line to examine (0..getLineCount() - 1)
4104     * @param bounds Optional. If not null, it returns the extent of the line
4105     * @return the Y-coordinate of the baseline
4106     */
4107    public int getLineBounds(int line, Rect bounds) {
4108        if (mLayout == null) {
4109            if (bounds != null) {
4110                bounds.set(0, 0, 0, 0);
4111            }
4112            return 0;
4113        }
4114        else {
4115            int baseline = mLayout.getLineBounds(line, bounds);
4116
4117            int voffset = getExtendedPaddingTop();
4118            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4119                voffset += getVerticalOffset(true);
4120            }
4121            if (bounds != null) {
4122                bounds.offset(getCompoundPaddingLeft(), voffset);
4123            }
4124            return baseline + voffset;
4125        }
4126    }
4127
4128    @Override
4129    public int getBaseline() {
4130        if (mLayout == null) {
4131            return super.getBaseline();
4132        }
4133
4134        int voffset = 0;
4135        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4136            voffset = getVerticalOffset(true);
4137        }
4138
4139        return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
4140    }
4141
4142    @Override
4143    public boolean onKeyDown(int keyCode, KeyEvent event) {
4144        int which = doKeyDown(keyCode, event, null);
4145        if (which == 0) {
4146            // Go through default dispatching.
4147            return super.onKeyDown(keyCode, event);
4148        }
4149
4150        return true;
4151    }
4152
4153    @Override
4154    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4155        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
4156
4157        int which = doKeyDown(keyCode, down, event);
4158        if (which == 0) {
4159            // Go through default dispatching.
4160            return super.onKeyMultiple(keyCode, repeatCount, event);
4161        }
4162        if (which == -1) {
4163            // Consumed the whole thing.
4164            return true;
4165        }
4166
4167        repeatCount--;
4168
4169        // We are going to dispatch the remaining events to either the input
4170        // or movement method.  To do this, we will just send a repeated stream
4171        // of down and up events until we have done the complete repeatCount.
4172        // It would be nice if those interfaces had an onKeyMultiple() method,
4173        // but adding that is a more complicated change.
4174        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
4175        if (which == 1) {
4176            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4177            while (--repeatCount > 0) {
4178                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
4179                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4180            }
4181            if (mError != null && !mErrorWasChanged) {
4182                setError(null, null);
4183            }
4184
4185        } else if (which == 2) {
4186            mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4187            while (--repeatCount > 0) {
4188                mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
4189                mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4190            }
4191        }
4192
4193        return true;
4194    }
4195
4196    /**
4197     * Returns true if pressing ENTER in this field advances focus instead
4198     * of inserting the character.  This is true mostly in single-line fields,
4199     * but also in mail addresses and subjects which will display on multiple
4200     * lines but where it doesn't make sense to insert newlines.
4201     */
4202    private boolean shouldAdvanceFocusOnEnter() {
4203        if (mInput == null) {
4204            return false;
4205        }
4206
4207        if (mSingleLine) {
4208            return true;
4209        }
4210
4211        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4212            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4213
4214            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
4215                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
4216                return true;
4217            }
4218        }
4219
4220        return false;
4221    }
4222
4223    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
4224        if (!isEnabled()) {
4225            return 0;
4226        }
4227
4228        switch (keyCode) {
4229            case KeyEvent.KEYCODE_ENTER:
4230                // If ALT modifier is held, then we always insert a
4231                // newline character.
4232                if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
4233
4234                    // When mInputContentType is set, we know that we are
4235                    // running in a "modern" cupcake environment, so don't need
4236                    // to worry about the application trying to capture
4237                    // enter key events.
4238                    if (mInputContentType != null) {
4239
4240                        // If there is an action listener, given them a
4241                        // chance to consume the event.
4242                        if (mInputContentType.onEditorActionListener != null &&
4243                                mInputContentType.onEditorActionListener.onEditorAction(
4244                                this, EditorInfo.IME_NULL, event)) {
4245                            mInputContentType.enterDown = true;
4246                            // We are consuming the enter key for them.
4247                            return -1;
4248                        }
4249                    }
4250
4251                    // If our editor should move focus when enter is pressed, or
4252                    // this is a generated event from an IME action button, then
4253                    // don't let it be inserted into the text.
4254                    if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4255                            || shouldAdvanceFocusOnEnter()) {
4256                        return -1;
4257                    }
4258                }
4259                break;
4260
4261            case KeyEvent.KEYCODE_DPAD_CENTER:
4262                if (shouldAdvanceFocusOnEnter()) {
4263                    return 0;
4264                }
4265        }
4266
4267        if (mInput != null) {
4268            /*
4269             * Keep track of what the error was before doing the input
4270             * so that if an input filter changed the error, we leave
4271             * that error showing.  Otherwise, we take down whatever
4272             * error was showing when the user types something.
4273             */
4274            mErrorWasChanged = false;
4275
4276            boolean doDown = true;
4277            if (otherEvent != null) {
4278                try {
4279                    beginBatchEdit();
4280                    boolean handled = mInput.onKeyOther(this, (Editable) mText,
4281                            otherEvent);
4282                    if (mError != null && !mErrorWasChanged) {
4283                        setError(null, null);
4284                    }
4285                    doDown = false;
4286                    if (handled) {
4287                        return -1;
4288                    }
4289                } catch (AbstractMethodError e) {
4290                    // onKeyOther was added after 1.0, so if it isn't
4291                    // implemented we need to try to dispatch as a regular down.
4292                } finally {
4293                    endBatchEdit();
4294                }
4295            }
4296
4297            if (doDown) {
4298                beginBatchEdit();
4299                if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
4300                    endBatchEdit();
4301                    if (mError != null && !mErrorWasChanged) {
4302                        setError(null, null);
4303                    }
4304                    return 1;
4305                }
4306                endBatchEdit();
4307            }
4308        }
4309
4310        // bug 650865: sometimes we get a key event before a layout.
4311        // don't try to move around if we don't know the layout.
4312
4313        if (mMovement != null && mLayout != null) {
4314            boolean doDown = true;
4315            if (otherEvent != null) {
4316                try {
4317                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
4318                            otherEvent);
4319                    doDown = false;
4320                    if (handled) {
4321                        return -1;
4322                    }
4323                } catch (AbstractMethodError e) {
4324                    // onKeyOther was added after 1.0, so if it isn't
4325                    // implemented we need to try to dispatch as a regular down.
4326                }
4327            }
4328            if (doDown) {
4329                if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
4330                    return 2;
4331            }
4332        }
4333
4334        return 0;
4335    }
4336
4337    @Override
4338    public boolean onKeyUp(int keyCode, KeyEvent event) {
4339        if (!isEnabled()) {
4340            return super.onKeyUp(keyCode, event);
4341        }
4342
4343        switch (keyCode) {
4344            case KeyEvent.KEYCODE_DPAD_CENTER:
4345                /*
4346                 * If there is a click listener, just call through to
4347                 * super, which will invoke it.
4348                 *
4349                 * If there isn't a click listener, try to show the soft
4350                 * input method.  (It will also
4351                 * call performClick(), but that won't do anything in
4352                 * this case.)
4353                 */
4354                if (mOnClickListener == null) {
4355                    if (mMovement != null && mText instanceof Editable
4356                            && mLayout != null && onCheckIsTextEditor()) {
4357                        InputMethodManager imm = (InputMethodManager)
4358                                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
4359                        imm.showSoftInput(this, 0);
4360                    }
4361                }
4362                return super.onKeyUp(keyCode, event);
4363
4364            case KeyEvent.KEYCODE_ENTER:
4365                if (mInputContentType != null
4366                        && mInputContentType.onEditorActionListener != null
4367                        && mInputContentType.enterDown) {
4368                    mInputContentType.enterDown = false;
4369                    if (mInputContentType.onEditorActionListener.onEditorAction(
4370                            this, EditorInfo.IME_NULL, event)) {
4371                        return true;
4372                    }
4373                }
4374
4375                if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4376                        || shouldAdvanceFocusOnEnter()) {
4377                    /*
4378                     * If there is a click listener, just call through to
4379                     * super, which will invoke it.
4380                     *
4381                     * If there isn't a click listener, try to advance focus,
4382                     * but still call through to super, which will reset the
4383                     * pressed state and longpress state.  (It will also
4384                     * call performClick(), but that won't do anything in
4385                     * this case.)
4386                     */
4387                    if (mOnClickListener == null) {
4388                        View v = focusSearch(FOCUS_DOWN);
4389
4390                        if (v != null) {
4391                            if (!v.requestFocus(FOCUS_DOWN)) {
4392                                throw new IllegalStateException("focus search returned a view " +
4393                                        "that wasn't able to take focus!");
4394                            }
4395
4396                            /*
4397                             * Return true because we handled the key; super
4398                             * will return false because there was no click
4399                             * listener.
4400                             */
4401                            super.onKeyUp(keyCode, event);
4402                            return true;
4403                        } else if ((event.getFlags()
4404                                & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4405                            // No target for next focus, but make sure the IME
4406                            // if this came from it.
4407                            InputMethodManager imm = InputMethodManager.peekInstance();
4408                            if (imm != null) {
4409                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
4410                            }
4411                        }
4412                    }
4413
4414                    return super.onKeyUp(keyCode, event);
4415                }
4416        }
4417
4418        if (mInput != null)
4419            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
4420                return true;
4421
4422        if (mMovement != null && mLayout != null)
4423            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
4424                return true;
4425
4426        return super.onKeyUp(keyCode, event);
4427    }
4428
4429    @Override public boolean onCheckIsTextEditor() {
4430        return mInputType != EditorInfo.TYPE_NULL;
4431    }
4432
4433    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4434        if (onCheckIsTextEditor()) {
4435            if (mInputMethodState == null) {
4436                mInputMethodState = new InputMethodState();
4437            }
4438            outAttrs.inputType = mInputType;
4439            if (mInputContentType != null) {
4440                outAttrs.imeOptions = mInputContentType.imeOptions;
4441                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
4442                outAttrs.actionLabel = mInputContentType.imeActionLabel;
4443                outAttrs.actionId = mInputContentType.imeActionId;
4444                outAttrs.extras = mInputContentType.extras;
4445            } else {
4446                outAttrs.imeOptions = EditorInfo.IME_NULL;
4447            }
4448            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
4449                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
4450                if (focusSearch(FOCUS_DOWN) != null) {
4451                    // An action has not been set, but the enter key will move to
4452                    // the next focus, so set the action to that.
4453                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
4454                } else {
4455                    // An action has not been set, and there is no focus to move
4456                    // to, so let's just supply a "done" action.
4457                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
4458                }
4459                if (!shouldAdvanceFocusOnEnter()) {
4460                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4461                }
4462            }
4463            if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS
4464                    | InputType.TYPE_TEXT_FLAG_MULTI_LINE))
4465                    == (InputType.TYPE_CLASS_TEXT
4466                            | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) {
4467                // Multi-line text editors should always show an enter key.
4468                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4469            }
4470            outAttrs.hintText = mHint;
4471            if (mText instanceof Editable) {
4472                InputConnection ic = new EditableInputConnection(this);
4473                outAttrs.initialSelStart = Selection.getSelectionStart(mText);
4474                outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
4475                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
4476                return ic;
4477            }
4478        }
4479        return null;
4480    }
4481
4482    /**
4483     * If this TextView contains editable content, extract a portion of it
4484     * based on the information in <var>request</var> in to <var>outText</var>.
4485     * @return Returns true if the text was successfully extracted, else false.
4486     */
4487    public boolean extractText(ExtractedTextRequest request,
4488            ExtractedText outText) {
4489        return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
4490                EXTRACT_UNKNOWN, outText);
4491    }
4492
4493    static final int EXTRACT_NOTHING = -2;
4494    static final int EXTRACT_UNKNOWN = -1;
4495
4496    boolean extractTextInternal(ExtractedTextRequest request,
4497            int partialStartOffset, int partialEndOffset, int delta,
4498            ExtractedText outText) {
4499        final CharSequence content = mText;
4500        if (content != null) {
4501            if (partialStartOffset != EXTRACT_NOTHING) {
4502                final int N = content.length();
4503                if (partialStartOffset < 0) {
4504                    outText.partialStartOffset = outText.partialEndOffset = -1;
4505                    partialStartOffset = 0;
4506                    partialEndOffset = N;
4507                } else {
4508                    // Adjust offsets to ensure we contain full spans.
4509                    if (content instanceof Spanned) {
4510                        Spanned spanned = (Spanned)content;
4511                        Object[] spans = spanned.getSpans(partialStartOffset,
4512                                partialEndOffset, ParcelableSpan.class);
4513                        int i = spans.length;
4514                        while (i > 0) {
4515                            i--;
4516                            int j = spanned.getSpanStart(spans[i]);
4517                            if (j < partialStartOffset) partialStartOffset = j;
4518                            j = spanned.getSpanEnd(spans[i]);
4519                            if (j > partialEndOffset) partialEndOffset = j;
4520                        }
4521                    }
4522                    outText.partialStartOffset = partialStartOffset;
4523                    outText.partialEndOffset = partialEndOffset;
4524                    // Now use the delta to determine the actual amount of text
4525                    // we need.
4526                    partialEndOffset += delta;
4527                    if (partialEndOffset > N) {
4528                        partialEndOffset = N;
4529                    } else if (partialEndOffset < 0) {
4530                        partialEndOffset = 0;
4531                    }
4532                }
4533                if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
4534                    outText.text = content.subSequence(partialStartOffset,
4535                            partialEndOffset);
4536                } else {
4537                    outText.text = TextUtils.substring(content, partialStartOffset,
4538                            partialEndOffset);
4539                }
4540            }
4541            outText.flags = 0;
4542            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
4543                outText.flags |= ExtractedText.FLAG_SELECTING;
4544            }
4545            if (mSingleLine) {
4546                outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
4547            }
4548            outText.startOffset = 0;
4549            outText.selectionStart = Selection.getSelectionStart(content);
4550            outText.selectionEnd = Selection.getSelectionEnd(content);
4551            return true;
4552        }
4553        return false;
4554    }
4555
4556    boolean reportExtractedText() {
4557        final InputMethodState ims = mInputMethodState;
4558        if (ims != null) {
4559            final boolean contentChanged = ims.mContentChanged;
4560            if (contentChanged || ims.mSelectionModeChanged) {
4561                ims.mContentChanged = false;
4562                ims.mSelectionModeChanged = false;
4563                final ExtractedTextRequest req = mInputMethodState.mExtracting;
4564                if (req != null) {
4565                    InputMethodManager imm = InputMethodManager.peekInstance();
4566                    if (imm != null) {
4567                        if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
4568                                + ims.mChangedStart + " end=" + ims.mChangedEnd
4569                                + " delta=" + ims.mChangedDelta);
4570                        if (ims.mChangedStart < 0 && !contentChanged) {
4571                            ims.mChangedStart = EXTRACT_NOTHING;
4572                        }
4573                        if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
4574                                ims.mChangedDelta, ims.mTmpExtracted)) {
4575                            if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
4576                                    + ims.mTmpExtracted.partialStartOffset
4577                                    + " end=" + ims.mTmpExtracted.partialEndOffset
4578                                    + ": " + ims.mTmpExtracted.text);
4579                            imm.updateExtractedText(this, req.token,
4580                                    mInputMethodState.mTmpExtracted);
4581                            return true;
4582                        }
4583                    }
4584                }
4585            }
4586        }
4587        return false;
4588    }
4589
4590    /**
4591     * This is used to remove all style-impacting spans from text before new
4592     * extracted text is being replaced into it, so that we don't have any
4593     * lingering spans applied during the replace.
4594     */
4595    static void removeParcelableSpans(Spannable spannable, int start, int end) {
4596        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
4597        int i = spans.length;
4598        while (i > 0) {
4599            i--;
4600            spannable.removeSpan(spans[i]);
4601        }
4602    }
4603
4604    /**
4605     * Apply to this text view the given extracted text, as previously
4606     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
4607     */
4608    public void setExtractedText(ExtractedText text) {
4609        Editable content = getEditableText();
4610        if (text.text != null) {
4611            if (content == null) {
4612                setText(text.text, TextView.BufferType.EDITABLE);
4613            } else if (text.partialStartOffset < 0) {
4614                removeParcelableSpans(content, 0, content.length());
4615                content.replace(0, content.length(), text.text);
4616            } else {
4617                final int N = content.length();
4618                int start = text.partialStartOffset;
4619                if (start > N) start = N;
4620                int end = text.partialEndOffset;
4621                if (end > N) end = N;
4622                removeParcelableSpans(content, start, end);
4623                content.replace(start, end, text.text);
4624            }
4625        }
4626
4627        // Now set the selection position...  make sure it is in range, to
4628        // avoid crashes.  If this is a partial update, it is possible that
4629        // the underlying text may have changed, causing us problems here.
4630        // Also we just don't want to trust clients to do the right thing.
4631        Spannable sp = (Spannable)getText();
4632        final int N = sp.length();
4633        int start = text.selectionStart;
4634        if (start < 0) start = 0;
4635        else if (start > N) start = N;
4636        int end = text.selectionEnd;
4637        if (end < 0) end = 0;
4638        else if (end > N) end = N;
4639        Selection.setSelection(sp, start, end);
4640
4641        // Finally, update the selection mode.
4642        if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
4643            MetaKeyKeyListener.startSelecting(this, sp);
4644        } else {
4645            MetaKeyKeyListener.stopSelecting(this, sp);
4646        }
4647    }
4648
4649    /**
4650     * @hide
4651     */
4652    public void setExtracting(ExtractedTextRequest req) {
4653        if (mInputMethodState != null) {
4654            mInputMethodState.mExtracting = req;
4655        }
4656    }
4657
4658    /**
4659     * Called by the framework in response to a text completion from
4660     * the current input method, provided by it calling
4661     * {@link InputConnection#commitCompletion
4662     * InputConnection.commitCompletion()}.  The default implementation does
4663     * nothing; text views that are supporting auto-completion should override
4664     * this to do their desired behavior.
4665     *
4666     * @param text The auto complete text the user has selected.
4667     */
4668    public void onCommitCompletion(CompletionInfo text) {
4669    }
4670
4671    public void beginBatchEdit() {
4672        final InputMethodState ims = mInputMethodState;
4673        if (ims != null) {
4674            int nesting = ++ims.mBatchEditNesting;
4675            if (nesting == 1) {
4676                ims.mCursorChanged = false;
4677                ims.mChangedDelta = 0;
4678                if (ims.mContentChanged) {
4679                    // We already have a pending change from somewhere else,
4680                    // so turn this into a full update.
4681                    ims.mChangedStart = 0;
4682                    ims.mChangedEnd = mText.length();
4683                } else {
4684                    ims.mChangedStart = EXTRACT_UNKNOWN;
4685                    ims.mChangedEnd = EXTRACT_UNKNOWN;
4686                    ims.mContentChanged = false;
4687                }
4688                onBeginBatchEdit();
4689            }
4690        }
4691    }
4692
4693    public void endBatchEdit() {
4694        final InputMethodState ims = mInputMethodState;
4695        if (ims != null) {
4696            int nesting = --ims.mBatchEditNesting;
4697            if (nesting == 0) {
4698                finishBatchEdit(ims);
4699            }
4700        }
4701    }
4702
4703    void ensureEndedBatchEdit() {
4704        final InputMethodState ims = mInputMethodState;
4705        if (ims != null && ims.mBatchEditNesting != 0) {
4706            ims.mBatchEditNesting = 0;
4707            finishBatchEdit(ims);
4708        }
4709    }
4710
4711    void finishBatchEdit(final InputMethodState ims) {
4712        onEndBatchEdit();
4713
4714        if (ims.mContentChanged || ims.mSelectionModeChanged) {
4715            updateAfterEdit();
4716            reportExtractedText();
4717        } else if (ims.mCursorChanged) {
4718            // Cheezy way to get us to report the current cursor location.
4719            invalidateCursor();
4720        }
4721    }
4722
4723    void updateAfterEdit() {
4724        invalidate();
4725        int curs = Selection.getSelectionStart(mText);
4726
4727        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4728                             Gravity.BOTTOM) {
4729            registerForPreDraw();
4730        }
4731
4732        if (curs >= 0) {
4733            mHighlightPathBogus = true;
4734
4735            if (isFocused()) {
4736                mShowCursor = SystemClock.uptimeMillis();
4737                makeBlink();
4738            }
4739        }
4740
4741        checkForResize();
4742    }
4743
4744    /**
4745     * Called by the framework in response to a request to begin a batch
4746     * of edit operations through a call to link {@link #beginBatchEdit()}.
4747     */
4748    public void onBeginBatchEdit() {
4749    }
4750
4751    /**
4752     * Called by the framework in response to a request to end a batch
4753     * of edit operations through a call to link {@link #endBatchEdit}.
4754     */
4755    public void onEndBatchEdit() {
4756    }
4757
4758    /**
4759     * Called by the framework in response to a private command from the
4760     * current method, provided by it calling
4761     * {@link InputConnection#performPrivateCommand
4762     * InputConnection.performPrivateCommand()}.
4763     *
4764     * @param action The action name of the command.
4765     * @param data Any additional data for the command.  This may be null.
4766     * @return Return true if you handled the command, else false.
4767     */
4768    public boolean onPrivateIMECommand(String action, Bundle data) {
4769        return false;
4770    }
4771
4772    private void nullLayouts() {
4773        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
4774            mSavedLayout = (BoringLayout) mLayout;
4775        }
4776        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
4777            mSavedHintLayout = (BoringLayout) mHintLayout;
4778        }
4779
4780        mLayout = mHintLayout = null;
4781    }
4782
4783    /**
4784     * Make a new Layout based on the already-measured size of the view,
4785     * on the assumption that it was measured correctly at some point.
4786     */
4787    private void assumeLayout() {
4788        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4789
4790        if (width < 1) {
4791            width = 0;
4792        }
4793
4794        int physicalWidth = width;
4795
4796        if (mHorizontallyScrolling) {
4797            width = VERY_WIDE;
4798        }
4799
4800        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
4801                      physicalWidth, false);
4802    }
4803
4804    /**
4805     * The width passed in is now the desired layout width,
4806     * not the full view width with padding.
4807     * {@hide}
4808     */
4809    protected void makeNewLayout(int w, int hintWidth,
4810                                 BoringLayout.Metrics boring,
4811                                 BoringLayout.Metrics hintBoring,
4812                                 int ellipsisWidth, boolean bringIntoView) {
4813        stopMarquee();
4814
4815        mHighlightPathBogus = true;
4816
4817        if (w < 0) {
4818            w = 0;
4819        }
4820        if (hintWidth < 0) {
4821            hintWidth = 0;
4822        }
4823
4824        Layout.Alignment alignment;
4825        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
4826            case Gravity.CENTER_HORIZONTAL:
4827                alignment = Layout.Alignment.ALIGN_CENTER;
4828                break;
4829
4830            case Gravity.RIGHT:
4831                alignment = Layout.Alignment.ALIGN_OPPOSITE;
4832                break;
4833
4834            default:
4835                alignment = Layout.Alignment.ALIGN_NORMAL;
4836        }
4837
4838        boolean shouldEllipsize = mEllipsize != null && mInput == null;
4839
4840        if (mText instanceof Spannable) {
4841            mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
4842                    alignment, mSpacingMult,
4843                    mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
4844                    ellipsisWidth);
4845        } else {
4846            if (boring == UNKNOWN_BORING) {
4847                boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4848                                               mBoring);
4849                if (boring != null) {
4850                    mBoring = boring;
4851                }
4852            }
4853
4854            if (boring != null) {
4855                if (boring.width <= w &&
4856                    (mEllipsize == null || boring.width <= ellipsisWidth)) {
4857                    if (mSavedLayout != null) {
4858                        mLayout = mSavedLayout.
4859                                replaceOrMake(mTransformed, mTextPaint,
4860                                w, alignment, mSpacingMult, mSpacingAdd,
4861                                boring, mIncludePad);
4862                    } else {
4863                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4864                                w, alignment, mSpacingMult, mSpacingAdd,
4865                                boring, mIncludePad);
4866                    }
4867                    // Log.e("aaa", "Boring: " + mTransformed);
4868
4869                    mSavedLayout = (BoringLayout) mLayout;
4870                } else if (shouldEllipsize && boring.width <= w) {
4871                    if (mSavedLayout != null) {
4872                        mLayout = mSavedLayout.
4873                                replaceOrMake(mTransformed, mTextPaint,
4874                                w, alignment, mSpacingMult, mSpacingAdd,
4875                                boring, mIncludePad, mEllipsize,
4876                                ellipsisWidth);
4877                    } else {
4878                        mLayout = BoringLayout.make(mTransformed, mTextPaint,
4879                                w, alignment, mSpacingMult, mSpacingAdd,
4880                                boring, mIncludePad, mEllipsize,
4881                                ellipsisWidth);
4882                    }
4883                } else if (shouldEllipsize) {
4884                    mLayout = new StaticLayout(mTransformed,
4885                                0, mTransformed.length(),
4886                                mTextPaint, w, alignment, mSpacingMult,
4887                                mSpacingAdd, mIncludePad, mEllipsize,
4888                                ellipsisWidth);
4889                } else {
4890                    mLayout = new StaticLayout(mTransformed, mTextPaint,
4891                            w, alignment, mSpacingMult, mSpacingAdd,
4892                            mIncludePad);
4893                    // Log.e("aaa", "Boring but wide: " + mTransformed);
4894                }
4895            } else if (shouldEllipsize) {
4896                mLayout = new StaticLayout(mTransformed,
4897                            0, mTransformed.length(),
4898                            mTextPaint, w, alignment, mSpacingMult,
4899                            mSpacingAdd, mIncludePad, mEllipsize,
4900                            ellipsisWidth);
4901            } else {
4902                mLayout = new StaticLayout(mTransformed, mTextPaint,
4903                        w, alignment, mSpacingMult, mSpacingAdd,
4904                        mIncludePad);
4905            }
4906        }
4907
4908        shouldEllipsize = mEllipsize != null;
4909        mHintLayout = null;
4910
4911        if (mHint != null) {
4912            if (shouldEllipsize) hintWidth = w;
4913
4914            if (hintBoring == UNKNOWN_BORING) {
4915                hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
4916                                                   mHintBoring);
4917                if (hintBoring != null) {
4918                    mHintBoring = hintBoring;
4919                }
4920            }
4921
4922            if (hintBoring != null) {
4923                if (hintBoring.width <= hintWidth &&
4924                    (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
4925                    if (mSavedHintLayout != null) {
4926                        mHintLayout = mSavedHintLayout.
4927                                replaceOrMake(mHint, mTextPaint,
4928                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
4929                                hintBoring, mIncludePad);
4930                    } else {
4931                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
4932                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
4933                                hintBoring, mIncludePad);
4934                    }
4935
4936                    mSavedHintLayout = (BoringLayout) mHintLayout;
4937                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
4938                    if (mSavedHintLayout != null) {
4939                        mHintLayout = mSavedHintLayout.
4940                                replaceOrMake(mHint, mTextPaint,
4941                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
4942                                hintBoring, mIncludePad, mEllipsize,
4943                                ellipsisWidth);
4944                    } else {
4945                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
4946                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
4947                                hintBoring, mIncludePad, mEllipsize,
4948                                ellipsisWidth);
4949                    }
4950                } else if (shouldEllipsize) {
4951                    mHintLayout = new StaticLayout(mHint,
4952                                0, mHint.length(),
4953                                mTextPaint, hintWidth, alignment, mSpacingMult,
4954                                mSpacingAdd, mIncludePad, mEllipsize,
4955                                ellipsisWidth);
4956                } else {
4957                    mHintLayout = new StaticLayout(mHint, mTextPaint,
4958                            hintWidth, alignment, mSpacingMult, mSpacingAdd,
4959                            mIncludePad);
4960                }
4961            } else if (shouldEllipsize) {
4962                mHintLayout = new StaticLayout(mHint,
4963                            0, mHint.length(),
4964                            mTextPaint, hintWidth, alignment, mSpacingMult,
4965                            mSpacingAdd, mIncludePad, mEllipsize,
4966                            ellipsisWidth);
4967            } else {
4968                mHintLayout = new StaticLayout(mHint, mTextPaint,
4969                        hintWidth, alignment, mSpacingMult, mSpacingAdd,
4970                        mIncludePad);
4971            }
4972        }
4973
4974        if (bringIntoView) {
4975            registerForPreDraw();
4976        }
4977
4978        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4979            if (!compressText(ellipsisWidth)) {
4980                final int height = mLayoutParams.height;
4981                // If the size of the view does not depend on the size of the text, try to
4982                // start the marquee immediately
4983                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
4984                    startMarquee();
4985                } else {
4986                    // Defer the start of the marquee until we know our width (see setFrame())
4987                    mRestartMarquee = true;
4988                }
4989            }
4990        }
4991    }
4992
4993    private boolean compressText(float width) {
4994        // Only compress the text if it hasn't been compressed by the previous pass
4995        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
4996                mTextPaint.getTextScaleX() == 1.0f) {
4997            final float textWidth = mLayout.getLineWidth(0);
4998            final float overflow = (textWidth + 1.0f - width) / width;
4999            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
5000                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
5001                post(new Runnable() {
5002                    public void run() {
5003                        requestLayout();
5004                    }
5005                });
5006                return true;
5007            }
5008        }
5009
5010        return false;
5011    }
5012
5013    private static int desired(Layout layout) {
5014        int n = layout.getLineCount();
5015        CharSequence text = layout.getText();
5016        float max = 0;
5017
5018        // if any line was wrapped, we can't use it.
5019        // but it's ok for the last line not to have a newline
5020
5021        for (int i = 0; i < n - 1; i++) {
5022            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
5023                return -1;
5024        }
5025
5026        for (int i = 0; i < n; i++) {
5027            max = Math.max(max, layout.getLineWidth(i));
5028        }
5029
5030        return (int) FloatMath.ceil(max);
5031    }
5032
5033    /**
5034     * Set whether the TextView includes extra top and bottom padding to make
5035     * room for accents that go above the normal ascent and descent.
5036     * The default is true.
5037     *
5038     * @attr ref android.R.styleable#TextView_includeFontPadding
5039     */
5040    public void setIncludeFontPadding(boolean includepad) {
5041        mIncludePad = includepad;
5042
5043        if (mLayout != null) {
5044            nullLayouts();
5045            requestLayout();
5046            invalidate();
5047        }
5048    }
5049
5050    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
5051
5052    @Override
5053    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5054        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
5055        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
5056        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
5057        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
5058
5059        int width;
5060        int height;
5061
5062        BoringLayout.Metrics boring = UNKNOWN_BORING;
5063        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
5064
5065        int des = -1;
5066        boolean fromexisting = false;
5067
5068        if (widthMode == MeasureSpec.EXACTLY) {
5069            // Parent has told us how big to be. So be it.
5070            width = widthSize;
5071        } else {
5072            if (mLayout != null && mEllipsize == null) {
5073                des = desired(mLayout);
5074            }
5075
5076            if (des < 0) {
5077                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
5078                if (boring != null) {
5079                    mBoring = boring;
5080                }
5081            } else {
5082                fromexisting = true;
5083            }
5084
5085            if (boring == null || boring == UNKNOWN_BORING) {
5086                if (des < 0) {
5087                    des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
5088                }
5089
5090                width = des;
5091            } else {
5092                width = boring.width;
5093            }
5094
5095            final Drawables dr = mDrawables;
5096            if (dr != null) {
5097                width = Math.max(width, dr.mDrawableWidthTop);
5098                width = Math.max(width, dr.mDrawableWidthBottom);
5099            }
5100
5101            if (mHint != null) {
5102                int hintDes = -1;
5103                int hintWidth;
5104
5105                if (mHintLayout != null && mEllipsize == null) {
5106                    hintDes = desired(mHintLayout);
5107                }
5108
5109                if (hintDes < 0) {
5110                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
5111                    if (hintBoring != null) {
5112                        mHintBoring = hintBoring;
5113                    }
5114                }
5115
5116                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
5117                    if (hintDes < 0) {
5118                        hintDes = (int) FloatMath.ceil(
5119                                Layout.getDesiredWidth(mHint, mTextPaint));
5120                    }
5121
5122                    hintWidth = hintDes;
5123                } else {
5124                    hintWidth = hintBoring.width;
5125                }
5126
5127                if (hintWidth > width) {
5128                    width = hintWidth;
5129                }
5130            }
5131
5132            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
5133
5134            if (mMaxWidthMode == EMS) {
5135                width = Math.min(width, mMaxWidth * getLineHeight());
5136            } else {
5137                width = Math.min(width, mMaxWidth);
5138            }
5139
5140            if (mMinWidthMode == EMS) {
5141                width = Math.max(width, mMinWidth * getLineHeight());
5142            } else {
5143                width = Math.max(width, mMinWidth);
5144            }
5145
5146            // Check against our minimum width
5147            width = Math.max(width, getSuggestedMinimumWidth());
5148
5149            if (widthMode == MeasureSpec.AT_MOST) {
5150                width = Math.min(widthSize, width);
5151            }
5152        }
5153
5154        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5155        int unpaddedWidth = want;
5156        int hintWant = want;
5157
5158        if (mHorizontallyScrolling)
5159            want = VERY_WIDE;
5160
5161        int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5162
5163        if (mLayout == null) {
5164            makeNewLayout(want, hintWant, boring, hintBoring,
5165                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5166        } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5167                   (mLayout.getEllipsizedWidth() !=
5168                        width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5169            if (mHint == null && mEllipsize == null &&
5170                    want > mLayout.getWidth() &&
5171                    (mLayout instanceof BoringLayout ||
5172                            (fromexisting && des >= 0 && des <= want))) {
5173                mLayout.increaseWidthTo(want);
5174            } else {
5175                makeNewLayout(want, hintWant, boring, hintBoring,
5176                              width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5177            }
5178        } else {
5179            // Width has not changed.
5180        }
5181
5182        if (heightMode == MeasureSpec.EXACTLY) {
5183            // Parent has told us how big to be. So be it.
5184            height = heightSize;
5185            mDesiredHeightAtMeasure = -1;
5186        } else {
5187            int desired = getDesiredHeight();
5188
5189            height = desired;
5190            mDesiredHeightAtMeasure = desired;
5191
5192            if (heightMode == MeasureSpec.AT_MOST) {
5193                height = Math.min(desired, height);
5194            }
5195        }
5196
5197        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
5198        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
5199            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
5200        }
5201
5202        /*
5203         * We didn't let makeNewLayout() register to bring the cursor into view,
5204         * so do it here if there is any possibility that it is needed.
5205         */
5206        if (mMovement != null ||
5207            mLayout.getWidth() > unpaddedWidth ||
5208            mLayout.getHeight() > unpaddedHeight) {
5209            registerForPreDraw();
5210        } else {
5211            scrollTo(0, 0);
5212        }
5213
5214        setMeasuredDimension(width, height);
5215    }
5216
5217    private int getDesiredHeight() {
5218        return Math.max(
5219                getDesiredHeight(mLayout, true),
5220                getDesiredHeight(mHintLayout, mEllipsize != null));
5221    }
5222
5223    private int getDesiredHeight(Layout layout, boolean cap) {
5224        if (layout == null) {
5225            return 0;
5226        }
5227
5228        int linecount = layout.getLineCount();
5229        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5230        int desired = layout.getLineTop(linecount);
5231
5232        final Drawables dr = mDrawables;
5233        if (dr != null) {
5234            desired = Math.max(desired, dr.mDrawableHeightLeft);
5235            desired = Math.max(desired, dr.mDrawableHeightRight);
5236        }
5237
5238        desired += pad;
5239
5240        if (mMaxMode == LINES) {
5241            /*
5242             * Don't cap the hint to a certain number of lines.
5243             * (Do cap it, though, if we have a maximum pixel height.)
5244             */
5245            if (cap) {
5246                if (linecount > mMaximum) {
5247                    desired = layout.getLineTop(mMaximum) +
5248                              layout.getBottomPadding();
5249
5250                    if (dr != null) {
5251                        desired = Math.max(desired, dr.mDrawableHeightLeft);
5252                        desired = Math.max(desired, dr.mDrawableHeightRight);
5253                    }
5254
5255                    desired += pad;
5256                    linecount = mMaximum;
5257                }
5258            }
5259        } else {
5260            desired = Math.min(desired, mMaximum);
5261        }
5262
5263        if (mMinMode == LINES) {
5264            if (linecount < mMinimum) {
5265                desired += getLineHeight() * (mMinimum - linecount);
5266            }
5267        } else {
5268            desired = Math.max(desired, mMinimum);
5269        }
5270
5271        // Check against our minimum height
5272        desired = Math.max(desired, getSuggestedMinimumHeight());
5273
5274        return desired;
5275    }
5276
5277    /**
5278     * Check whether a change to the existing text layout requires a
5279     * new view layout.
5280     */
5281    private void checkForResize() {
5282        boolean sizeChanged = false;
5283
5284        if (mLayout != null) {
5285            // Check if our width changed
5286            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
5287                sizeChanged = true;
5288                invalidate();
5289            }
5290
5291            // Check if our height changed
5292            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
5293                int desiredHeight = getDesiredHeight();
5294
5295                if (desiredHeight != this.getHeight()) {
5296                    sizeChanged = true;
5297                }
5298            } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
5299                if (mDesiredHeightAtMeasure >= 0) {
5300                    int desiredHeight = getDesiredHeight();
5301
5302                    if (desiredHeight != mDesiredHeightAtMeasure) {
5303                        sizeChanged = true;
5304                    }
5305                }
5306            }
5307        }
5308
5309        if (sizeChanged) {
5310            requestLayout();
5311            // caller will have already invalidated
5312        }
5313    }
5314
5315    /**
5316     * Check whether entirely new text requires a new view layout
5317     * or merely a new text layout.
5318     */
5319    private void checkForRelayout() {
5320        // If we have a fixed width, we can just swap in a new text layout
5321        // if the text height stays the same or if the view height is fixed.
5322
5323        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
5324                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
5325                (mHint == null || mHintLayout != null) &&
5326                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
5327            // Static width, so try making a new text layout.
5328
5329            int oldht = mLayout.getHeight();
5330            int want = mLayout.getWidth();
5331            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5332
5333            /*
5334             * No need to bring the text into view, since the size is not
5335             * changing (unless we do the requestLayout(), in which case it
5336             * will happen at measure).
5337             */
5338            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5339                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5340
5341            // In a fixed-height view, so use our new text layout.
5342            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
5343                mLayoutParams.height != LayoutParams.FILL_PARENT) {
5344                invalidate();
5345                return;
5346            }
5347
5348            // Dynamic height, but height has stayed the same,
5349            // so use our new text layout.
5350            if (mLayout.getHeight() == oldht &&
5351                (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
5352                invalidate();
5353                return;
5354            }
5355
5356            // We lose: the height has changed and we have a dynamic height.
5357            // Request a new view layout using our new text layout.
5358            requestLayout();
5359            invalidate();
5360        } else {
5361            // Dynamic width, so we have no choice but to request a new
5362            // view layout with a new text layout.
5363
5364            nullLayouts();
5365            requestLayout();
5366            invalidate();
5367        }
5368    }
5369
5370    /**
5371     * Returns true if anything changed.
5372     */
5373    private boolean bringTextIntoView() {
5374        int line = 0;
5375        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5376            line = mLayout.getLineCount() - 1;
5377        }
5378
5379        Layout.Alignment a = mLayout.getParagraphAlignment(line);
5380        int dir = mLayout.getParagraphDirection(line);
5381        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5382        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5383        int ht = mLayout.getHeight();
5384
5385        int scrollx, scrolly;
5386
5387        if (a == Layout.Alignment.ALIGN_CENTER) {
5388            /*
5389             * Keep centered if possible, or, if it is too wide to fit,
5390             * keep leading edge in view.
5391             */
5392
5393            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5394            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5395
5396            if (right - left < hspace) {
5397                scrollx = (right + left) / 2 - hspace / 2;
5398            } else {
5399                if (dir < 0) {
5400                    scrollx = right - hspace;
5401                } else {
5402                    scrollx = left;
5403                }
5404            }
5405        } else if (a == Layout.Alignment.ALIGN_NORMAL) {
5406            /*
5407             * Keep leading edge in view.
5408             */
5409
5410            if (dir < 0) {
5411                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5412                scrollx = right - hspace;
5413            } else {
5414                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5415            }
5416        } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
5417            /*
5418             * Keep trailing edge in view.
5419             */
5420
5421            if (dir < 0) {
5422                scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5423            } else {
5424                int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5425                scrollx = right - hspace;
5426            }
5427        }
5428
5429        if (ht < vspace) {
5430            scrolly = 0;
5431        } else {
5432            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5433                scrolly = ht - vspace;
5434            } else {
5435                scrolly = 0;
5436            }
5437        }
5438
5439        if (scrollx != mScrollX || scrolly != mScrollY) {
5440            scrollTo(scrollx, scrolly);
5441            return true;
5442        } else {
5443            return false;
5444        }
5445    }
5446
5447    /**
5448     * Move the point, specified by the offset, into the view if it is needed.
5449     * This has to be called after layout. Returns true if anything changed.
5450     */
5451    public boolean bringPointIntoView(int offset) {
5452        boolean changed = false;
5453
5454        int line = mLayout.getLineForOffset(offset);
5455
5456        // FIXME: Is it okay to truncate this, or should we round?
5457        final int x = (int)mLayout.getPrimaryHorizontal(offset);
5458        final int top = mLayout.getLineTop(line);
5459        final int bottom = mLayout.getLineTop(line+1);
5460
5461        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5462        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5463        int ht = mLayout.getHeight();
5464
5465        int grav;
5466
5467        switch (mLayout.getParagraphAlignment(line)) {
5468            case ALIGN_NORMAL:
5469                grav = 1;
5470                break;
5471
5472            case ALIGN_OPPOSITE:
5473                grav = -1;
5474                break;
5475
5476            default:
5477                grav = 0;
5478        }
5479
5480        grav *= mLayout.getParagraphDirection(line);
5481
5482        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5483        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5484
5485        int hslack = (bottom - top) / 2;
5486        int vslack = hslack;
5487
5488        if (vslack > vspace / 4)
5489            vslack = vspace / 4;
5490        if (hslack > hspace / 4)
5491            hslack = hspace / 4;
5492
5493        int hs = mScrollX;
5494        int vs = mScrollY;
5495
5496        if (top - vs < vslack)
5497            vs = top - vslack;
5498        if (bottom - vs > vspace - vslack)
5499            vs = bottom - (vspace - vslack);
5500        if (ht - vs < vspace)
5501            vs = ht - vspace;
5502        if (0 - vs > 0)
5503            vs = 0;
5504
5505        if (grav != 0) {
5506            if (x - hs < hslack) {
5507                hs = x - hslack;
5508            }
5509            if (x - hs > hspace - hslack) {
5510                hs = x - (hspace - hslack);
5511            }
5512        }
5513
5514        if (grav < 0) {
5515            if (left - hs > 0)
5516                hs = left;
5517            if (right - hs < hspace)
5518                hs = right - hspace;
5519        } else if (grav > 0) {
5520            if (right - hs < hspace)
5521                hs = right - hspace;
5522            if (left - hs > 0)
5523                hs = left;
5524        } else /* grav == 0 */ {
5525            if (right - left <= hspace) {
5526                /*
5527                 * If the entire text fits, center it exactly.
5528                 */
5529                hs = left - (hspace - (right - left)) / 2;
5530            } else if (x > right - hslack) {
5531                /*
5532                 * If we are near the right edge, keep the right edge
5533                 * at the edge of the view.
5534                 */
5535                hs = right - hspace;
5536            } else if (x < left + hslack) {
5537                /*
5538                 * If we are near the left edge, keep the left edge
5539                 * at the edge of the view.
5540                 */
5541                hs = left;
5542            } else if (left > hs) {
5543                /*
5544                 * Is there whitespace visible at the left?  Fix it if so.
5545                 */
5546                hs = left;
5547            } else if (right < hs + hspace) {
5548                /*
5549                 * Is there whitespace visible at the right?  Fix it if so.
5550                 */
5551                hs = right - hspace;
5552            } else {
5553                /*
5554                 * Otherwise, float as needed.
5555                 */
5556                if (x - hs < hslack) {
5557                    hs = x - hslack;
5558                }
5559                if (x - hs > hspace - hslack) {
5560                    hs = x - (hspace - hslack);
5561                }
5562            }
5563        }
5564
5565        if (hs != mScrollX || vs != mScrollY) {
5566            if (mScroller == null) {
5567                scrollTo(hs, vs);
5568            } else {
5569                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
5570                int dx = hs - mScrollX;
5571                int dy = vs - mScrollY;
5572
5573                if (duration > ANIMATED_SCROLL_GAP) {
5574                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
5575                    invalidate();
5576                } else {
5577                    if (!mScroller.isFinished()) {
5578                        mScroller.abortAnimation();
5579                    }
5580
5581                    scrollBy(dx, dy);
5582                }
5583
5584                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
5585            }
5586
5587            changed = true;
5588        }
5589
5590        if (isFocused()) {
5591            // This offsets because getInterestingRect() is in terms of
5592            // viewport coordinates, but requestRectangleOnScreen()
5593            // is in terms of content coordinates.
5594
5595            Rect r = new Rect();
5596            getInterestingRect(r, x, top, bottom, line);
5597            r.offset(mScrollX, mScrollY);
5598
5599            if (requestRectangleOnScreen(r)) {
5600                changed = true;
5601            }
5602        }
5603
5604        return changed;
5605    }
5606
5607    /**
5608     * Move the cursor, if needed, so that it is at an offset that is visible
5609     * to the user.  This will not move the cursor if it represents more than
5610     * one character (a selection range).  This will only work if the
5611     * TextView contains spannable text; otherwise it will do nothing.
5612     */
5613    public boolean moveCursorToVisibleOffset() {
5614        if (!(mText instanceof Spannable)) {
5615            return false;
5616        }
5617        int start = Selection.getSelectionStart(mText);
5618        int end = Selection.getSelectionEnd(mText);
5619        if (start != end) {
5620            return false;
5621        }
5622
5623        // First: make sure the line is visible on screen:
5624
5625        int line = mLayout.getLineForOffset(start);
5626
5627        final int top = mLayout.getLineTop(line);
5628        final int bottom = mLayout.getLineTop(line+1);
5629        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5630        int vslack = (bottom - top) / 2;
5631        if (vslack > vspace / 4)
5632            vslack = vspace / 4;
5633        final int vs = mScrollY;
5634
5635        if (top < (vs+vslack)) {
5636            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
5637        } else if (bottom > (vspace+vs-vslack)) {
5638            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
5639        }
5640
5641        // Next: make sure the character is visible on screen:
5642
5643        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5644        final int hs = mScrollX;
5645        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
5646        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
5647
5648        int newStart = start;
5649        if (newStart < leftChar) {
5650            newStart = leftChar;
5651        } else if (newStart > rightChar) {
5652            newStart = rightChar;
5653        }
5654
5655        if (newStart != start) {
5656            Selection.setSelection((Spannable)mText, newStart);
5657            return true;
5658        }
5659
5660        return false;
5661    }
5662
5663    @Override
5664    public void computeScroll() {
5665        if (mScroller != null) {
5666            if (mScroller.computeScrollOffset()) {
5667                mScrollX = mScroller.getCurrX();
5668                mScrollY = mScroller.getCurrY();
5669                postInvalidate();  // So we draw again
5670            }
5671        }
5672    }
5673
5674    private void getInterestingRect(Rect r, int h, int top, int bottom,
5675                                    int line) {
5676        int paddingTop = getExtendedPaddingTop();
5677        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5678            paddingTop += getVerticalOffset(false);
5679        }
5680        top += paddingTop;
5681        bottom += paddingTop;
5682        h += getCompoundPaddingLeft();
5683
5684        if (line == 0)
5685            top -= getExtendedPaddingTop();
5686        if (line == mLayout.getLineCount() - 1)
5687            bottom += getExtendedPaddingBottom();
5688
5689        r.set(h, top, h+1, bottom);
5690        r.offset(-mScrollX, -mScrollY);
5691    }
5692
5693    @Override
5694    public void debug(int depth) {
5695        super.debug(depth);
5696
5697        String output = debugIndent(depth);
5698        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
5699                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
5700                + "} ";
5701
5702        if (mText != null) {
5703
5704            output += "mText=\"" + mText + "\" ";
5705            if (mLayout != null) {
5706                output += "mLayout width=" + mLayout.getWidth()
5707                        + " height=" + mLayout.getHeight();
5708            }
5709        } else {
5710            output += "mText=NULL";
5711        }
5712        Log.d(VIEW_LOG_TAG, output);
5713    }
5714
5715    /**
5716     * Convenience for {@link Selection#getSelectionStart}.
5717     */
5718    public int getSelectionStart() {
5719        return Selection.getSelectionStart(getText());
5720    }
5721
5722    /**
5723     * Convenience for {@link Selection#getSelectionEnd}.
5724     */
5725    public int getSelectionEnd() {
5726        return Selection.getSelectionEnd(getText());
5727    }
5728
5729    /**
5730     * Return true iff there is a selection inside this text view.
5731     */
5732    public boolean hasSelection() {
5733        return getSelectionStart() != getSelectionEnd();
5734    }
5735
5736    /**
5737     * Sets the properties of this field (lines, horizontally scrolling,
5738     * transformation method) to be for a single-line input.
5739     *
5740     * @attr ref android.R.styleable#TextView_singleLine
5741     */
5742    public void setSingleLine() {
5743        setSingleLine(true);
5744    }
5745
5746    /**
5747     * If true, sets the properties of this field (lines, horizontally
5748     * scrolling, transformation method) to be for a single-line input;
5749     * if false, restores these to the default conditions.
5750     * Note that calling this with false restores default conditions,
5751     * not necessarily those that were in effect prior to calling
5752     * it with true.
5753     *
5754     * @attr ref android.R.styleable#TextView_singleLine
5755     */
5756    @android.view.RemotableViewMethod
5757    public void setSingleLine(boolean singleLine) {
5758        if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
5759                == EditorInfo.TYPE_CLASS_TEXT) {
5760            if (singleLine) {
5761                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5762            } else {
5763                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5764            }
5765        }
5766        applySingleLine(singleLine, true);
5767    }
5768
5769    private void applySingleLine(boolean singleLine, boolean applyTransformation) {
5770        mSingleLine = singleLine;
5771        if (singleLine) {
5772            setLines(1);
5773            setHorizontallyScrolling(true);
5774            if (applyTransformation) {
5775                setTransformationMethod(SingleLineTransformationMethod.
5776                                        getInstance());
5777            }
5778        } else {
5779            setMaxLines(Integer.MAX_VALUE);
5780            setHorizontallyScrolling(false);
5781            if (applyTransformation) {
5782                setTransformationMethod(null);
5783            }
5784        }
5785    }
5786
5787    /**
5788     * Causes words in the text that are longer than the view is wide
5789     * to be ellipsized instead of broken in the middle.  You may also
5790     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
5791     * to constrain the text to a single line.  Use <code>null</code>
5792     * to turn off ellipsizing.
5793     *
5794     * @attr ref android.R.styleable#TextView_ellipsize
5795     */
5796    public void setEllipsize(TextUtils.TruncateAt where) {
5797        mEllipsize = where;
5798
5799        if (mLayout != null) {
5800            nullLayouts();
5801            requestLayout();
5802            invalidate();
5803        }
5804    }
5805
5806    /**
5807     * Sets how many times to repeat the marquee animation. Only applied if the
5808     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
5809     *
5810     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
5811     */
5812    public void setMarqueeRepeatLimit(int marqueeLimit) {
5813        mMarqueeRepeatLimit = marqueeLimit;
5814    }
5815
5816    /**
5817     * Returns where, if anywhere, words that are longer than the view
5818     * is wide should be ellipsized.
5819     */
5820    @ViewDebug.ExportedProperty
5821    public TextUtils.TruncateAt getEllipsize() {
5822        return mEllipsize;
5823    }
5824
5825    /**
5826     * Set the TextView so that when it takes focus, all the text is
5827     * selected.
5828     *
5829     * @attr ref android.R.styleable#TextView_selectAllOnFocus
5830     */
5831    @android.view.RemotableViewMethod
5832    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
5833        mSelectAllOnFocus = selectAllOnFocus;
5834
5835        if (selectAllOnFocus && !(mText instanceof Spannable)) {
5836            setText(mText, BufferType.SPANNABLE);
5837        }
5838    }
5839
5840    /**
5841     * Set whether the cursor is visible.  The default is true.
5842     *
5843     * @attr ref android.R.styleable#TextView_cursorVisible
5844     */
5845    @android.view.RemotableViewMethod
5846    public void setCursorVisible(boolean visible) {
5847        mCursorVisible = visible;
5848        invalidate();
5849
5850        if (visible) {
5851            makeBlink();
5852        } else if (mBlink != null) {
5853            mBlink.removeCallbacks(mBlink);
5854        }
5855    }
5856
5857    private boolean canMarquee() {
5858        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
5859        return width > 0 && mLayout.getLineWidth(0) > width;
5860    }
5861
5862    private void startMarquee() {
5863        // Do not ellipsize EditText
5864        if (mInput != null) return;
5865
5866        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5867            return;
5868        }
5869
5870        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
5871                getLineCount() == 1 && canMarquee()) {
5872
5873            if (mMarquee == null) mMarquee = new Marquee(this);
5874            mMarquee.start(mMarqueeRepeatLimit);
5875        }
5876    }
5877
5878    private void stopMarquee() {
5879        if (mMarquee != null && !mMarquee.isStopped()) {
5880            mMarquee.stop();
5881        }
5882    }
5883
5884    private void startStopMarquee(boolean start) {
5885        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5886            if (start) {
5887                startMarquee();
5888            } else {
5889                stopMarquee();
5890            }
5891        }
5892    }
5893
5894    private static final class Marquee extends Handler {
5895        // TODO: Add an option to configure this
5896        private static final float MARQUEE_DELTA_MAX = 0.07f;
5897        private static final int MARQUEE_DELAY = 1200;
5898        private static final int MARQUEE_RESTART_DELAY = 1200;
5899        private static final int MARQUEE_RESOLUTION = 1000 / 30;
5900        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
5901
5902        private static final byte MARQUEE_STOPPED = 0x0;
5903        private static final byte MARQUEE_STARTING = 0x1;
5904        private static final byte MARQUEE_RUNNING = 0x2;
5905
5906        private static final int MESSAGE_START = 0x1;
5907        private static final int MESSAGE_TICK = 0x2;
5908        private static final int MESSAGE_RESTART = 0x3;
5909
5910        private final WeakReference<TextView> mView;
5911
5912        private byte mStatus = MARQUEE_STOPPED;
5913        private float mScrollUnit;
5914        private float mMaxScroll;
5915        float mMaxFadeScroll;
5916        private float mGhostStart;
5917        private float mGhostOffset;
5918        private float mFadeStop;
5919        private int mRepeatLimit;
5920
5921        float mScroll;
5922
5923        Marquee(TextView v) {
5924            final float density = v.getContext().getResources().getDisplayMetrics().density;
5925            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
5926            mView = new WeakReference<TextView>(v);
5927        }
5928
5929        @Override
5930        public void handleMessage(Message msg) {
5931            switch (msg.what) {
5932                case MESSAGE_START:
5933                    mStatus = MARQUEE_RUNNING;
5934                    tick();
5935                    break;
5936                case MESSAGE_TICK:
5937                    tick();
5938                    break;
5939                case MESSAGE_RESTART:
5940                    if (mStatus == MARQUEE_RUNNING) {
5941                        if (mRepeatLimit >= 0) {
5942                            mRepeatLimit--;
5943                        }
5944                        start(mRepeatLimit);
5945                    }
5946                    break;
5947            }
5948        }
5949
5950        void tick() {
5951            if (mStatus != MARQUEE_RUNNING) {
5952                return;
5953            }
5954
5955            removeMessages(MESSAGE_TICK);
5956
5957            final TextView textView = mView.get();
5958            if (textView != null && (textView.isFocused() || textView.isSelected())) {
5959                mScroll += mScrollUnit;
5960                if (mScroll > mMaxScroll) {
5961                    mScroll = mMaxScroll;
5962                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
5963                } else {
5964                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
5965                }
5966                textView.invalidate();
5967            }
5968        }
5969
5970        void stop() {
5971            mStatus = MARQUEE_STOPPED;
5972            removeMessages(MESSAGE_START);
5973            removeMessages(MESSAGE_RESTART);
5974            removeMessages(MESSAGE_TICK);
5975            resetScroll();
5976        }
5977
5978        private void resetScroll() {
5979            mScroll = 0.0f;
5980            final TextView textView = mView.get();
5981            if (textView != null) textView.invalidate();
5982        }
5983
5984        void start(int repeatLimit) {
5985            if (repeatLimit == 0) {
5986                stop();
5987                return;
5988            }
5989            mRepeatLimit = repeatLimit;
5990            final TextView textView = mView.get();
5991            if (textView != null && textView.mLayout != null) {
5992                mStatus = MARQUEE_STARTING;
5993                mScroll = 0.0f;
5994                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
5995                        textView.getCompoundPaddingRight();
5996                final float lineWidth = textView.mLayout.getLineWidth(0);
5997                final float gap = textWidth / 3.0f;
5998                mGhostStart = lineWidth - textWidth + gap;
5999                mMaxScroll = mGhostStart + textWidth;
6000                mGhostOffset = lineWidth + gap;
6001                mFadeStop = lineWidth + textWidth / 6.0f;
6002                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
6003
6004                textView.invalidate();
6005                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
6006            }
6007        }
6008
6009        float getGhostOffset() {
6010            return mGhostOffset;
6011        }
6012
6013        boolean shouldDrawLeftFade() {
6014            return mScroll <= mFadeStop;
6015        }
6016
6017        boolean shouldDrawGhost() {
6018            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
6019        }
6020
6021        boolean isRunning() {
6022            return mStatus == MARQUEE_RUNNING;
6023        }
6024
6025        boolean isStopped() {
6026            return mStatus == MARQUEE_STOPPED;
6027        }
6028    }
6029
6030    /**
6031     * This method is called when the text is changed, in case any
6032     * subclasses would like to know.
6033     *
6034     * @param text The text the TextView is displaying.
6035     * @param start The offset of the start of the range of the text
6036     *              that was modified.
6037     * @param before The offset of the former end of the range of the
6038     *               text that was modified.  If text was simply inserted,
6039     *               this will be the same as <code>start</code>.
6040     *               If text was replaced with new text or deleted, the
6041     *               length of the old text was <code>before-start</code>.
6042     * @param after The offset of the end of the range of the text
6043     *              that was modified.  If text was simply deleted,
6044     *              this will be the same as <code>start</code>.
6045     *              If text was replaced with new text or inserted,
6046     *              the length of the new text is <code>after-start</code>.
6047     */
6048    protected void onTextChanged(CharSequence text,
6049                                 int start, int before, int after) {
6050    }
6051
6052    /**
6053     * This method is called when the selection has changed, in case any
6054     * subclasses would like to know.
6055     *
6056     * @param selStart The new selection start location.
6057     * @param selEnd The new selection end location.
6058     */
6059    protected void onSelectionChanged(int selStart, int selEnd) {
6060    }
6061
6062    /**
6063     * Adds a TextWatcher to the list of those whose methods are called
6064     * whenever this TextView's text changes.
6065     * <p>
6066     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6067     * not called after {@link #setText} calls.  Now, doing {@link #setText}
6068     * if there are any text changed listeners forces the buffer type to
6069     * Editable if it would not otherwise be and does call this method.
6070     */
6071    public void addTextChangedListener(TextWatcher watcher) {
6072        if (mListeners == null) {
6073            mListeners = new ArrayList<TextWatcher>();
6074        }
6075
6076        mListeners.add(watcher);
6077    }
6078
6079    /**
6080     * Removes the specified TextWatcher from the list of those whose
6081     * methods are called
6082     * whenever this TextView's text changes.
6083     */
6084    public void removeTextChangedListener(TextWatcher watcher) {
6085        if (mListeners != null) {
6086            int i = mListeners.indexOf(watcher);
6087
6088            if (i >= 0) {
6089                mListeners.remove(i);
6090            }
6091        }
6092    }
6093
6094    private void sendBeforeTextChanged(CharSequence text, int start, int before,
6095                                   int after) {
6096        if (mListeners != null) {
6097            final ArrayList<TextWatcher> list = mListeners;
6098            final int count = list.size();
6099            for (int i = 0; i < count; i++) {
6100                list.get(i).beforeTextChanged(text, start, before, after);
6101            }
6102        }
6103    }
6104
6105    /**
6106     * Not private so it can be called from an inner class without going
6107     * through a thunk.
6108     */
6109    void sendOnTextChanged(CharSequence text, int start, int before,
6110                                   int after) {
6111        if (mListeners != null) {
6112            final ArrayList<TextWatcher> list = mListeners;
6113            final int count = list.size();
6114            for (int i = 0; i < count; i++) {
6115                list.get(i).onTextChanged(text, start, before, after);
6116            }
6117        }
6118    }
6119
6120    /**
6121     * Not private so it can be called from an inner class without going
6122     * through a thunk.
6123     */
6124    void sendAfterTextChanged(Editable text) {
6125        if (mListeners != null) {
6126            final ArrayList<TextWatcher> list = mListeners;
6127            final int count = list.size();
6128            for (int i = 0; i < count; i++) {
6129                list.get(i).afterTextChanged(text);
6130            }
6131        }
6132    }
6133
6134    /**
6135     * Not private so it can be called from an inner class without going
6136     * through a thunk.
6137     */
6138    void handleTextChanged(CharSequence buffer, int start,
6139            int before, int after) {
6140        final InputMethodState ims = mInputMethodState;
6141        if (ims == null || ims.mBatchEditNesting == 0) {
6142            updateAfterEdit();
6143        }
6144        if (ims != null) {
6145            ims.mContentChanged = true;
6146            if (ims.mChangedStart < 0) {
6147                ims.mChangedStart = start;
6148                ims.mChangedEnd = start+before;
6149            } else {
6150                if (ims.mChangedStart > start) ims.mChangedStart = start;
6151                if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
6152            }
6153            ims.mChangedDelta += after-before;
6154        }
6155
6156        sendOnTextChanged(buffer, start, before, after);
6157        onTextChanged(buffer, start, before, after);
6158    }
6159
6160    /**
6161     * Not private so it can be called from an inner class without going
6162     * through a thunk.
6163     */
6164    void spanChange(Spanned buf, Object what, int oldStart, int newStart,
6165            int oldEnd, int newEnd) {
6166        // XXX Make the start and end move together if this ends up
6167        // spending too much time invalidating.
6168
6169        boolean selChanged = false;
6170        int newSelStart=-1, newSelEnd=-1;
6171
6172        final InputMethodState ims = mInputMethodState;
6173
6174        if (what == Selection.SELECTION_END) {
6175            mHighlightPathBogus = true;
6176            selChanged = true;
6177            newSelEnd = newStart;
6178
6179            if (!isFocused()) {
6180                mSelectionMoved = true;
6181            }
6182
6183            if (oldStart >= 0 || newStart >= 0) {
6184                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6185                registerForPreDraw();
6186
6187                if (isFocused()) {
6188                    mShowCursor = SystemClock.uptimeMillis();
6189                    makeBlink();
6190                }
6191            }
6192        }
6193
6194        if (what == Selection.SELECTION_START) {
6195            mHighlightPathBogus = true;
6196            selChanged = true;
6197            newSelStart = newStart;
6198
6199            if (!isFocused()) {
6200                mSelectionMoved = true;
6201            }
6202
6203            if (oldStart >= 0 || newStart >= 0) {
6204                int end = Selection.getSelectionEnd(buf);
6205                invalidateCursor(end, oldStart, newStart);
6206            }
6207        }
6208
6209        if (selChanged) {
6210            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6211                if (newSelStart < 0) {
6212                    newSelStart = Selection.getSelectionStart(buf);
6213                }
6214                if (newSelEnd < 0) {
6215                    newSelEnd = Selection.getSelectionEnd(buf);
6216                }
6217                onSelectionChanged(newSelStart, newSelEnd);
6218            }
6219        }
6220
6221        if (what instanceof UpdateAppearance ||
6222            what instanceof ParagraphStyle) {
6223            if (ims == null || ims.mBatchEditNesting == 0) {
6224                invalidate();
6225                mHighlightPathBogus = true;
6226                checkForResize();
6227            } else {
6228                ims.mContentChanged = true;
6229            }
6230        }
6231
6232        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6233            mHighlightPathBogus = true;
6234            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6235                ims.mSelectionModeChanged = true;
6236            }
6237
6238            if (Selection.getSelectionStart(buf) >= 0) {
6239                if (ims == null || ims.mBatchEditNesting == 0) {
6240                    invalidateCursor();
6241                } else {
6242                    ims.mCursorChanged = true;
6243                }
6244            }
6245        }
6246
6247        if (what instanceof ParcelableSpan) {
6248            // If this is a span that can be sent to a remote process,
6249            // the current extract editor would be interested in it.
6250            if (ims != null && ims.mExtracting != null) {
6251                if (ims.mBatchEditNesting != 0) {
6252                    if (oldStart >= 0) {
6253                        if (ims.mChangedStart > oldStart) {
6254                            ims.mChangedStart = oldStart;
6255                        }
6256                        if (ims.mChangedStart > oldEnd) {
6257                            ims.mChangedStart = oldEnd;
6258                        }
6259                    }
6260                    if (newStart >= 0) {
6261                        if (ims.mChangedStart > newStart) {
6262                            ims.mChangedStart = newStart;
6263                        }
6264                        if (ims.mChangedStart > newEnd) {
6265                            ims.mChangedStart = newEnd;
6266                        }
6267                    }
6268                } else {
6269                    if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
6270                            + oldStart + "-" + oldEnd + ","
6271                            + newStart + "-" + newEnd + what);
6272                    ims.mContentChanged = true;
6273                }
6274            }
6275        }
6276    }
6277
6278    private class ChangeWatcher
6279    implements TextWatcher, SpanWatcher {
6280
6281        private CharSequence mBeforeText;
6282
6283        public void beforeTextChanged(CharSequence buffer, int start,
6284                                      int before, int after) {
6285            if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
6286                    + " before=" + before + " after=" + after + ": " + buffer);
6287
6288            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
6289                mBeforeText = buffer.toString();
6290            }
6291
6292            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
6293        }
6294
6295        public void onTextChanged(CharSequence buffer, int start,
6296                                  int before, int after) {
6297            if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
6298                    + " before=" + before + " after=" + after + ": " + buffer);
6299            TextView.this.handleTextChanged(buffer, start, before, after);
6300
6301            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
6302                    (isFocused() || isSelected() &&
6303                    isShown())) {
6304                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
6305                mBeforeText = null;
6306            }
6307        }
6308
6309        public void afterTextChanged(Editable buffer) {
6310            if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
6311            TextView.this.sendAfterTextChanged(buffer);
6312
6313            if (MetaKeyKeyListener.getMetaState(buffer,
6314                                 MetaKeyKeyListener.META_SELECTING) != 0) {
6315                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
6316            }
6317        }
6318
6319        public void onSpanChanged(Spannable buf,
6320                                  Object what, int s, int e, int st, int en) {
6321            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
6322                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
6323            TextView.this.spanChange(buf, what, s, st, e, en);
6324        }
6325
6326        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
6327            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
6328                    + " what=" + what + ": " + buf);
6329            TextView.this.spanChange(buf, what, -1, s, -1, e);
6330        }
6331
6332        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
6333            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
6334                    + " what=" + what + ": " + buf);
6335            TextView.this.spanChange(buf, what, s, -1, e, -1);
6336        }
6337    }
6338
6339    private void makeBlink() {
6340        if (!mCursorVisible) {
6341            if (mBlink != null) {
6342                mBlink.removeCallbacks(mBlink);
6343            }
6344
6345            return;
6346        }
6347
6348        if (mBlink == null)
6349            mBlink = new Blink(this);
6350
6351        mBlink.removeCallbacks(mBlink);
6352        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
6353    }
6354
6355    @Override
6356    public void onStartTemporaryDetach() {
6357        mTemporaryDetach = true;
6358    }
6359
6360    @Override
6361    public void onFinishTemporaryDetach() {
6362        mTemporaryDetach = false;
6363    }
6364
6365    @Override
6366    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
6367        if (mTemporaryDetach) {
6368            // If we are temporarily in the detach state, then do nothing.
6369            super.onFocusChanged(focused, direction, previouslyFocusedRect);
6370            return;
6371        }
6372
6373        mShowCursor = SystemClock.uptimeMillis();
6374
6375        ensureEndedBatchEdit();
6376
6377        if (focused) {
6378            int selStart = getSelectionStart();
6379            int selEnd = getSelectionEnd();
6380
6381            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
6382                boolean selMoved = mSelectionMoved;
6383
6384                if (mMovement != null) {
6385                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
6386                }
6387
6388                if (mSelectAllOnFocus) {
6389                    Selection.setSelection((Spannable) mText, 0, mText.length());
6390                }
6391
6392                if (selMoved && selStart >= 0 && selEnd >= 0) {
6393                    /*
6394                     * Someone intentionally set the selection, so let them
6395                     * do whatever it is that they wanted to do instead of
6396                     * the default on-focus behavior.  We reset the selection
6397                     * here instead of just skipping the onTakeFocus() call
6398                     * because some movement methods do something other than
6399                     * just setting the selection in theirs and we still
6400                     * need to go through that path.
6401                     */
6402
6403                    Selection.setSelection((Spannable) mText, selStart, selEnd);
6404                }
6405                mTouchFocusSelected = true;
6406            }
6407
6408            mFrozenWithFocus = false;
6409            mSelectionMoved = false;
6410
6411            if (mText instanceof Spannable) {
6412                Spannable sp = (Spannable) mText;
6413                MetaKeyKeyListener.resetMetaState(sp);
6414            }
6415
6416            makeBlink();
6417
6418            if (mError != null) {
6419                showError();
6420            }
6421        } else {
6422            if (mError != null) {
6423                hideError();
6424            }
6425            // Don't leave us in the middle of a batch edit.
6426            onEndBatchEdit();
6427        }
6428
6429        startStopMarquee(focused);
6430
6431        if (mTransformation != null) {
6432            mTransformation.onFocusChanged(this, mText, focused, direction,
6433                                           previouslyFocusedRect);
6434        }
6435
6436        super.onFocusChanged(focused, direction, previouslyFocusedRect);
6437    }
6438
6439    @Override
6440    public void onWindowFocusChanged(boolean hasWindowFocus) {
6441        super.onWindowFocusChanged(hasWindowFocus);
6442
6443        if (hasWindowFocus) {
6444            if (mBlink != null) {
6445                mBlink.uncancel();
6446
6447                if (isFocused()) {
6448                    mShowCursor = SystemClock.uptimeMillis();
6449                    makeBlink();
6450                }
6451            }
6452        } else {
6453            if (mBlink != null) {
6454                mBlink.cancel();
6455            }
6456            // Don't leave us in the middle of a batch edit.
6457            onEndBatchEdit();
6458            if (mInputContentType != null) {
6459                mInputContentType.enterDown = false;
6460            }
6461        }
6462
6463        startStopMarquee(hasWindowFocus);
6464    }
6465
6466    /**
6467     * Use {@link BaseInputConnection#removeComposingSpans
6468     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
6469     * state from this text view.
6470     */
6471    public void clearComposingText() {
6472        if (mText instanceof Spannable) {
6473            BaseInputConnection.removeComposingSpans((Spannable)mText);
6474        }
6475    }
6476
6477    @Override
6478    public void setSelected(boolean selected) {
6479        boolean wasSelected = isSelected();
6480
6481        super.setSelected(selected);
6482
6483        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6484            if (selected) {
6485                startMarquee();
6486            } else {
6487                stopMarquee();
6488            }
6489        }
6490    }
6491
6492    class CommitSelectionReceiver extends ResultReceiver {
6493        int mNewStart;
6494        int mNewEnd;
6495
6496        CommitSelectionReceiver() {
6497            super(getHandler());
6498        }
6499
6500        protected void onReceiveResult(int resultCode, Bundle resultData) {
6501            if (resultCode != InputMethodManager.RESULT_SHOWN) {
6502                final int len = mText.length();
6503                if (mNewStart > len) {
6504                    mNewStart = len;
6505                }
6506                if (mNewEnd > len) {
6507                    mNewEnd = len;
6508                }
6509                Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
6510            }
6511        }
6512    }
6513
6514    @Override
6515    public boolean onTouchEvent(MotionEvent event) {
6516        final int action = event.getAction();
6517        if (action == MotionEvent.ACTION_DOWN) {
6518            // Reset this state; it will be re-set if super.onTouchEvent
6519            // causes focus to move to the view.
6520            mTouchFocusSelected = false;
6521        }
6522
6523        final boolean superResult = super.onTouchEvent(event);
6524
6525        /*
6526         * Don't handle the release after a long press, because it will
6527         * move the selection away from whatever the menu action was
6528         * trying to affect.
6529         */
6530        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
6531            mEatTouchRelease = false;
6532            return superResult;
6533        }
6534
6535        if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
6536
6537            if (action == MotionEvent.ACTION_DOWN) {
6538                mScrolled = false;
6539            }
6540
6541            boolean handled = false;
6542
6543            int oldSelStart = Selection.getSelectionStart(mText);
6544            int oldSelEnd = Selection.getSelectionEnd(mText);
6545
6546            if (mMovement != null) {
6547                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
6548            }
6549
6550            if (mText instanceof Editable && onCheckIsTextEditor()) {
6551                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
6552                    InputMethodManager imm = (InputMethodManager)
6553                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
6554
6555                    // This is going to be gross...  if tapping on the text view
6556                    // causes the IME to be displayed, we don't want the selection
6557                    // to change.  But the selection has already changed, and
6558                    // we won't know right away whether the IME is getting
6559                    // displayed, so...
6560
6561                    int newSelStart = Selection.getSelectionStart(mText);
6562                    int newSelEnd = Selection.getSelectionEnd(mText);
6563                    CommitSelectionReceiver csr = null;
6564                    if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
6565                        csr = new CommitSelectionReceiver();
6566                        csr.mNewStart = newSelStart;
6567                        csr.mNewEnd = newSelEnd;
6568                    }
6569
6570                    if (imm.showSoftInput(this, 0, csr) && csr != null) {
6571                        // The IME might get shown -- revert to the old
6572                        // selection, and change to the new when we finally
6573                        // find out of it is okay.
6574                        Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
6575                        handled = true;
6576                    }
6577                }
6578            }
6579
6580            if (handled) {
6581                return true;
6582            }
6583        }
6584
6585        return superResult;
6586    }
6587
6588    /**
6589     * Returns true, only while processing a touch gesture, if the initial
6590     * touch down event caused focus to move to the text view and as a result
6591     * its selection changed.  Only valid while processing the touch gesture
6592     * of interest.
6593     */
6594    public boolean didTouchFocusSelect() {
6595        return mTouchFocusSelected;
6596    }
6597
6598    @Override
6599    public void cancelLongPress() {
6600        super.cancelLongPress();
6601        mScrolled = true;
6602    }
6603
6604    @Override
6605    public boolean onTrackballEvent(MotionEvent event) {
6606        if (mMovement != null && mText instanceof Spannable &&
6607            mLayout != null) {
6608            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
6609                return true;
6610            }
6611        }
6612
6613        return super.onTrackballEvent(event);
6614    }
6615
6616    public void setScroller(Scroller s) {
6617        mScroller = s;
6618    }
6619
6620    private static class Blink extends Handler implements Runnable {
6621        private WeakReference<TextView> mView;
6622        private boolean mCancelled;
6623
6624        public Blink(TextView v) {
6625            mView = new WeakReference<TextView>(v);
6626        }
6627
6628        public void run() {
6629            if (mCancelled) {
6630                return;
6631            }
6632
6633            removeCallbacks(Blink.this);
6634
6635            TextView tv = mView.get();
6636
6637            if (tv != null && tv.isFocused()) {
6638                int st = Selection.getSelectionStart(tv.mText);
6639                int en = Selection.getSelectionEnd(tv.mText);
6640
6641                if (st == en && st >= 0 && en >= 0) {
6642                    if (tv.mLayout != null) {
6643                        tv.invalidateCursorPath();
6644                    }
6645
6646                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
6647                }
6648            }
6649        }
6650
6651        void cancel() {
6652            if (!mCancelled) {
6653                removeCallbacks(Blink.this);
6654                mCancelled = true;
6655            }
6656        }
6657
6658        void uncancel() {
6659            mCancelled = false;
6660        }
6661    }
6662
6663    @Override
6664    protected float getLeftFadingEdgeStrength() {
6665        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6666            if (mMarquee != null && !mMarquee.isStopped()) {
6667                final Marquee marquee = mMarquee;
6668                if (marquee.shouldDrawLeftFade()) {
6669                    return marquee.mScroll / getHorizontalFadingEdgeLength();
6670                } else {
6671                    return 0.0f;
6672                }
6673            } else if (getLineCount() == 1) {
6674                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6675                    case Gravity.LEFT:
6676                        return 0.0f;
6677                    case Gravity.RIGHT:
6678                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
6679                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
6680                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
6681                    case Gravity.CENTER_HORIZONTAL:
6682                        return 0.0f;
6683                }
6684            }
6685        }
6686        return super.getLeftFadingEdgeStrength();
6687    }
6688
6689    @Override
6690    protected float getRightFadingEdgeStrength() {
6691        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6692            if (mMarquee != null && !mMarquee.isStopped()) {
6693                final Marquee marquee = mMarquee;
6694                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
6695            } else if (getLineCount() == 1) {
6696                switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6697                    case Gravity.LEFT:
6698                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
6699                                getCompoundPaddingRight();
6700                        final float lineWidth = mLayout.getLineWidth(0);
6701                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
6702                    case Gravity.RIGHT:
6703                        return 0.0f;
6704                    case Gravity.CENTER_HORIZONTAL:
6705                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
6706                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
6707                                getHorizontalFadingEdgeLength();
6708                }
6709            }
6710        }
6711        return super.getRightFadingEdgeStrength();
6712    }
6713
6714    @Override
6715    protected int computeHorizontalScrollRange() {
6716        if (mLayout != null)
6717            return mLayout.getWidth();
6718
6719        return super.computeHorizontalScrollRange();
6720    }
6721
6722    @Override
6723    protected int computeVerticalScrollRange() {
6724        if (mLayout != null)
6725            return mLayout.getHeight();
6726
6727        return super.computeVerticalScrollRange();
6728    }
6729
6730    @Override
6731    protected int computeVerticalScrollExtent() {
6732        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
6733    }
6734
6735    public enum BufferType {
6736        NORMAL, SPANNABLE, EDITABLE,
6737    }
6738
6739    /**
6740     * Returns the TextView_textColor attribute from the
6741     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
6742     * from the TextView_textAppearance attribute, if TextView_textColor
6743     * was not set directly.
6744     */
6745    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
6746        ColorStateList colors;
6747        colors = attrs.getColorStateList(com.android.internal.R.styleable.
6748                                         TextView_textColor);
6749
6750        if (colors == null) {
6751            int ap = attrs.getResourceId(com.android.internal.R.styleable.
6752                                         TextView_textAppearance, -1);
6753            if (ap != -1) {
6754                TypedArray appearance;
6755                appearance = context.obtainStyledAttributes(ap,
6756                                            com.android.internal.R.styleable.TextAppearance);
6757                colors = appearance.getColorStateList(com.android.internal.R.styleable.
6758                                                  TextAppearance_textColor);
6759                appearance.recycle();
6760            }
6761        }
6762
6763        return colors;
6764    }
6765
6766    /**
6767     * Returns the default color from the TextView_textColor attribute
6768     * from the AttributeSet, if set, or the default color from the
6769     * TextAppearance_textColor from the TextView_textAppearance attribute,
6770     * if TextView_textColor was not set directly.
6771     */
6772    public static int getTextColor(Context context,
6773                                   TypedArray attrs,
6774                                   int def) {
6775        ColorStateList colors = getTextColors(context, attrs);
6776
6777        if (colors == null) {
6778            return def;
6779        } else {
6780            return colors.getDefaultColor();
6781        }
6782    }
6783
6784    @Override
6785    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
6786        switch (keyCode) {
6787        case KeyEvent.KEYCODE_A:
6788            if (canSelectAll()) {
6789                return onTextContextMenuItem(ID_SELECT_ALL);
6790            }
6791
6792            break;
6793
6794        case KeyEvent.KEYCODE_X:
6795            if (canCut()) {
6796                return onTextContextMenuItem(ID_CUT);
6797            }
6798
6799            break;
6800
6801        case KeyEvent.KEYCODE_C:
6802            if (canCopy()) {
6803                return onTextContextMenuItem(ID_COPY);
6804            }
6805
6806            break;
6807
6808        case KeyEvent.KEYCODE_V:
6809            if (canPaste()) {
6810                return onTextContextMenuItem(ID_PASTE);
6811            }
6812
6813            break;
6814        }
6815
6816        return super.onKeyShortcut(keyCode, event);
6817    }
6818
6819    private boolean canSelectAll() {
6820        if (mText instanceof Spannable && mText.length() != 0 &&
6821            mMovement != null && mMovement.canSelectArbitrarily()) {
6822            return true;
6823        }
6824
6825        return false;
6826    }
6827
6828    private boolean canSelectText() {
6829        if (mText instanceof Spannable && mText.length() != 0 &&
6830            mMovement != null && mMovement.canSelectArbitrarily()) {
6831            return true;
6832        }
6833
6834        return false;
6835    }
6836
6837    private boolean canCut() {
6838        if (mTransformation instanceof PasswordTransformationMethod) {
6839            return false;
6840        }
6841
6842        if (mText.length() > 0 && getSelectionStart() >= 0) {
6843            if (mText instanceof Editable && mInput != null) {
6844                return true;
6845            }
6846        }
6847
6848        return false;
6849    }
6850
6851    private boolean canCopy() {
6852        if (mTransformation instanceof PasswordTransformationMethod) {
6853            return false;
6854        }
6855
6856        if (mText.length() > 0 && getSelectionStart() >= 0) {
6857            return true;
6858        }
6859
6860        return false;
6861    }
6862
6863    private boolean canPaste() {
6864        if (mText instanceof Editable && mInput != null &&
6865            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
6866            ClipboardManager clip = (ClipboardManager)getContext()
6867                    .getSystemService(Context.CLIPBOARD_SERVICE);
6868            if (clip.hasText()) {
6869                return true;
6870            }
6871        }
6872
6873        return false;
6874    }
6875
6876    /**
6877     * Returns a word to add to the dictionary from the context menu,
6878     * or null if there is no cursor or no word at the cursor.
6879     */
6880    private String getWordForDictionary() {
6881        /*
6882         * Quick return if the input type is one where adding words
6883         * to the dictionary doesn't make any sense.
6884         */
6885        int klass = mInputType & InputType.TYPE_MASK_CLASS;
6886        if (klass == InputType.TYPE_CLASS_NUMBER ||
6887            klass == InputType.TYPE_CLASS_PHONE ||
6888            klass == InputType.TYPE_CLASS_DATETIME) {
6889            return null;
6890        }
6891
6892        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
6893        if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
6894            variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
6895            variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
6896            variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
6897            variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
6898            return null;
6899        }
6900
6901        int end = getSelectionEnd();
6902
6903        if (end < 0) {
6904            return null;
6905        }
6906
6907        int start = end;
6908        int len = mText.length();
6909
6910        for (; start > 0; start--) {
6911            char c = mTransformed.charAt(start - 1);
6912            int type = Character.getType(c);
6913
6914            if (c != '\'' &&
6915                type != Character.UPPERCASE_LETTER &&
6916                type != Character.LOWERCASE_LETTER &&
6917                type != Character.TITLECASE_LETTER &&
6918                type != Character.MODIFIER_LETTER &&
6919                type != Character.DECIMAL_DIGIT_NUMBER) {
6920                break;
6921            }
6922        }
6923
6924        for (; end < len; end++) {
6925            char c = mTransformed.charAt(end);
6926            int type = Character.getType(c);
6927
6928            if (c != '\'' &&
6929                type != Character.UPPERCASE_LETTER &&
6930                type != Character.LOWERCASE_LETTER &&
6931                type != Character.TITLECASE_LETTER &&
6932                type != Character.MODIFIER_LETTER &&
6933                type != Character.DECIMAL_DIGIT_NUMBER) {
6934                break;
6935            }
6936        }
6937
6938        boolean hasLetter = false;
6939        for (int i = start; i < end; i++) {
6940            if (Character.isLetter(mTransformed.charAt(i))) {
6941                hasLetter = true;
6942                break;
6943            }
6944        }
6945        if (!hasLetter) {
6946            return null;
6947        }
6948
6949        if (start == end) {
6950            return null;
6951        }
6952
6953        if (end - start > 48) {
6954            return null;
6955        }
6956
6957        return TextUtils.substring(mTransformed, start, end);
6958    }
6959
6960    @Override
6961    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
6962        boolean isPassword =
6963            (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) ==
6964            (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
6965
6966        if (!isPassword) {
6967            CharSequence text = getText();
6968            if (TextUtils.isEmpty(text)) {
6969                text = getHint();
6970            }
6971            if (!TextUtils.isEmpty(text)) {
6972                if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
6973                    text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
6974                }
6975                event.getText().add(text);
6976            }
6977        } else {
6978            event.setPassword(isPassword);
6979        }
6980        return false;
6981    }
6982
6983    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
6984            int fromIndex, int removedCount, int addedCount) {
6985        AccessibilityEvent event =
6986            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
6987        event.setFromIndex(fromIndex);
6988        event.setRemovedCount(removedCount);
6989        event.setAddedCount(addedCount);
6990        event.setBeforeText(beforeText);
6991        sendAccessibilityEventUnchecked(event);
6992    }
6993
6994    @Override
6995    protected void onCreateContextMenu(ContextMenu menu) {
6996        super.onCreateContextMenu(menu);
6997        boolean added = false;
6998
6999        if (!isFocused()) {
7000            if (isFocusable() && mInput != null) {
7001                if (canCopy()) {
7002                    MenuHandler handler = new MenuHandler();
7003                    int name = com.android.internal.R.string.copyAll;
7004
7005                    menu.add(0, ID_COPY, 0, name).
7006                        setOnMenuItemClickListener(handler).
7007                        setAlphabeticShortcut('c');
7008                    menu.setHeaderTitle(com.android.internal.R.string.
7009                        editTextMenuTitle);
7010                }
7011            }
7012
7013            return;
7014        }
7015
7016        MenuHandler handler = new MenuHandler();
7017
7018        if (canSelectAll()) {
7019            menu.add(0, ID_SELECT_ALL, 0,
7020                    com.android.internal.R.string.selectAll).
7021                setOnMenuItemClickListener(handler).
7022                setAlphabeticShortcut('a');
7023            added = true;
7024        }
7025
7026        boolean selection = getSelectionStart() != getSelectionEnd();
7027
7028        if (canSelectText()) {
7029            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
7030                menu.add(0, ID_STOP_SELECTING_TEXT, 0,
7031                        com.android.internal.R.string.stopSelectingText).
7032                    setOnMenuItemClickListener(handler);
7033                added = true;
7034            } else {
7035                menu.add(0, ID_START_SELECTING_TEXT, 0,
7036                        com.android.internal.R.string.selectText).
7037                    setOnMenuItemClickListener(handler);
7038                added = true;
7039            }
7040        }
7041
7042        if (canCut()) {
7043            int name;
7044            if (selection) {
7045                name = com.android.internal.R.string.cut;
7046            } else {
7047                name = com.android.internal.R.string.cutAll;
7048            }
7049
7050            menu.add(0, ID_CUT, 0, name).
7051                setOnMenuItemClickListener(handler).
7052                setAlphabeticShortcut('x');
7053            added = true;
7054        }
7055
7056        if (canCopy()) {
7057            int name;
7058            if (selection) {
7059                name = com.android.internal.R.string.copy;
7060            } else {
7061                name = com.android.internal.R.string.copyAll;
7062            }
7063
7064            menu.add(0, ID_COPY, 0, name).
7065                setOnMenuItemClickListener(handler).
7066                setAlphabeticShortcut('c');
7067            added = true;
7068        }
7069
7070        if (canPaste()) {
7071            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
7072                    setOnMenuItemClickListener(handler).
7073                    setAlphabeticShortcut('v');
7074            added = true;
7075        }
7076
7077        if (mText instanceof Spanned) {
7078            int selStart = getSelectionStart();
7079            int selEnd = getSelectionEnd();
7080
7081            int min = Math.min(selStart, selEnd);
7082            int max = Math.max(selStart, selEnd);
7083
7084            URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7085                                                        URLSpan.class);
7086            if (urls.length == 1) {
7087                menu.add(0, ID_COPY_URL, 0,
7088                         com.android.internal.R.string.copyUrl).
7089                            setOnMenuItemClickListener(handler);
7090                added = true;
7091            }
7092        }
7093
7094        if (isInputMethodTarget()) {
7095            menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
7096                    setOnMenuItemClickListener(handler);
7097            added = true;
7098        }
7099
7100        String word = getWordForDictionary();
7101        if (word != null) {
7102            menu.add(1, ID_ADD_TO_DICTIONARY, 0,
7103                     getContext().getString(com.android.internal.R.string.addToDictionary, word)).
7104                    setOnMenuItemClickListener(handler);
7105            added = true;
7106
7107        }
7108
7109        if (added) {
7110            menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7111        }
7112    }
7113
7114    /**
7115     * Returns whether this text view is a current input method target.  The
7116     * default implementation just checks with {@link InputMethodManager}.
7117     */
7118    public boolean isInputMethodTarget() {
7119        InputMethodManager imm = InputMethodManager.peekInstance();
7120        return imm != null && imm.isActive(this);
7121    }
7122
7123    private static final int ID_SELECT_ALL = android.R.id.selectAll;
7124    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
7125    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
7126    private static final int ID_CUT = android.R.id.cut;
7127    private static final int ID_COPY = android.R.id.copy;
7128    private static final int ID_PASTE = android.R.id.paste;
7129    private static final int ID_COPY_URL = android.R.id.copyUrl;
7130    private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
7131    private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
7132
7133    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
7134        public boolean onMenuItemClick(MenuItem item) {
7135            return onTextContextMenuItem(item.getItemId());
7136        }
7137    }
7138
7139    /**
7140     * Called when a context menu option for the text view is selected.  Currently
7141     * this will be one of: {@link android.R.id#selectAll},
7142     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
7143     * {@link android.R.id#cut}, {@link android.R.id#copy},
7144     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
7145     * or {@link android.R.id#switchInputMethod}.
7146     */
7147    public boolean onTextContextMenuItem(int id) {
7148        int selStart = getSelectionStart();
7149        int selEnd = getSelectionEnd();
7150
7151        if (!isFocused()) {
7152            selStart = 0;
7153            selEnd = mText.length();
7154        }
7155
7156        int min = Math.min(selStart, selEnd);
7157        int max = Math.max(selStart, selEnd);
7158
7159        if (min < 0) {
7160            min = 0;
7161        }
7162        if (max < 0) {
7163            max = 0;
7164        }
7165
7166        ClipboardManager clip = (ClipboardManager)getContext()
7167                .getSystemService(Context.CLIPBOARD_SERVICE);
7168
7169        switch (id) {
7170            case ID_SELECT_ALL:
7171                Selection.setSelection((Spannable) mText, 0,
7172                        mText.length());
7173                return true;
7174
7175            case ID_START_SELECTING_TEXT:
7176                MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
7177                return true;
7178
7179            case ID_STOP_SELECTING_TEXT:
7180                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7181                Selection.setSelection((Spannable) mText, getSelectionEnd());
7182                return true;
7183
7184            case ID_CUT:
7185                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7186
7187                if (min == max) {
7188                    min = 0;
7189                    max = mText.length();
7190                }
7191
7192                clip.setText(mTransformed.subSequence(min, max));
7193                ((Editable) mText).delete(min, max);
7194                return true;
7195
7196            case ID_COPY:
7197                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7198
7199                if (min == max) {
7200                    min = 0;
7201                    max = mText.length();
7202                }
7203
7204                clip.setText(mTransformed.subSequence(min, max));
7205                return true;
7206
7207            case ID_PASTE:
7208                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7209
7210                CharSequence paste = clip.getText();
7211
7212                if (paste != null) {
7213                    Selection.setSelection((Spannable) mText, max);
7214                    ((Editable) mText).replace(min, max, paste);
7215                }
7216
7217                return true;
7218
7219            case ID_COPY_URL:
7220                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7221
7222                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7223                                                       URLSpan.class);
7224                if (urls.length == 1) {
7225                    clip.setText(urls[0].getURL());
7226                }
7227
7228                return true;
7229
7230            case ID_SWITCH_INPUT_METHOD:
7231                InputMethodManager imm = InputMethodManager.peekInstance();
7232                if (imm != null) {
7233                    imm.showInputMethodPicker();
7234                }
7235                return true;
7236
7237            case ID_ADD_TO_DICTIONARY:
7238                String word = getWordForDictionary();
7239
7240                if (word != null) {
7241                    Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
7242                    i.putExtra("word", word);
7243                    i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
7244                    getContext().startActivity(i);
7245                }
7246
7247                return true;
7248            }
7249
7250        return false;
7251    }
7252
7253    public boolean performLongClick() {
7254        if (super.performLongClick()) {
7255            mEatTouchRelease = true;
7256            return true;
7257        }
7258
7259        return false;
7260    }
7261
7262    @ViewDebug.ExportedProperty
7263    private CharSequence            mText;
7264    private CharSequence            mTransformed;
7265    private BufferType              mBufferType = BufferType.NORMAL;
7266
7267    private int                     mInputType = EditorInfo.TYPE_NULL;
7268    private CharSequence            mHint;
7269    private Layout                  mHintLayout;
7270
7271    private KeyListener             mInput;
7272
7273    private MovementMethod          mMovement;
7274    private TransformationMethod    mTransformation;
7275    private ChangeWatcher           mChangeWatcher;
7276
7277    private ArrayList<TextWatcher>  mListeners = null;
7278
7279    // display attributes
7280    private TextPaint               mTextPaint;
7281    private boolean                 mUserSetTextScaleX;
7282    private Paint                   mHighlightPaint;
7283    private int                     mHighlightColor = 0xFFBBDDFF;
7284    private Layout                  mLayout;
7285
7286    private long                    mShowCursor;
7287    private Blink                   mBlink;
7288    private boolean                 mCursorVisible = true;
7289
7290    private boolean                 mSelectAllOnFocus = false;
7291
7292    private int                     mGravity = Gravity.TOP | Gravity.LEFT;
7293    private boolean                 mHorizontallyScrolling;
7294
7295    private int                     mAutoLinkMask;
7296    private boolean                 mLinksClickable = true;
7297
7298    private float                   mSpacingMult = 1;
7299    private float                   mSpacingAdd = 0;
7300
7301    private static final int        LINES = 1;
7302    private static final int        EMS = LINES;
7303    private static final int        PIXELS = 2;
7304
7305    private int                     mMaximum = Integer.MAX_VALUE;
7306    private int                     mMaxMode = LINES;
7307    private int                     mMinimum = 0;
7308    private int                     mMinMode = LINES;
7309
7310    private int                     mMaxWidth = Integer.MAX_VALUE;
7311    private int                     mMaxWidthMode = PIXELS;
7312    private int                     mMinWidth = 0;
7313    private int                     mMinWidthMode = PIXELS;
7314
7315    private boolean                 mSingleLine;
7316    private int                     mDesiredHeightAtMeasure = -1;
7317    private boolean                 mIncludePad = true;
7318
7319    // tmp primitives, so we don't alloc them on each draw
7320    private Path                    mHighlightPath;
7321    private boolean                 mHighlightPathBogus = true;
7322    private static final RectF      sTempRect = new RectF();
7323
7324    // XXX should be much larger
7325    private static final int        VERY_WIDE = 16384;
7326
7327    private static final int        BLINK = 500;
7328
7329    private static final int ANIMATED_SCROLL_GAP = 250;
7330    private long mLastScroll;
7331    private Scroller mScroller = null;
7332
7333    private BoringLayout.Metrics mBoring;
7334    private BoringLayout.Metrics mHintBoring;
7335
7336    private BoringLayout mSavedLayout, mSavedHintLayout;
7337
7338    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
7339    private InputFilter[] mFilters = NO_FILTERS;
7340    private static final Spanned EMPTY_SPANNED = new SpannedString("");
7341}
7342