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