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