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