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