TextView.java revision f17981b33b0359136c0b88a92311a3140be2ba71
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
6000                width = des;
6001            } else {
6002                width = boring.width;
6003            }
6004
6005            final Drawables dr = mDrawables;
6006            if (dr != null) {
6007                width = Math.max(width, dr.mDrawableWidthTop);
6008                width = Math.max(width, dr.mDrawableWidthBottom);
6009            }
6010
6011            if (mHint != null) {
6012                int hintDes = -1;
6013                int hintWidth;
6014
6015                if (mHintLayout != null && mEllipsize == null) {
6016                    hintDes = desired(mHintLayout);
6017                }
6018
6019                if (hintDes < 0) {
6020                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
6021                    if (hintBoring != null) {
6022                        mHintBoring = hintBoring;
6023                    }
6024                }
6025
6026                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6027                    if (hintDes < 0) {
6028                        hintDes = (int) FloatMath.ceil(
6029                                Layout.getDesiredWidth(mHint, mTextPaint));
6030                    }
6031
6032                    hintWidth = hintDes;
6033                } else {
6034                    hintWidth = hintBoring.width;
6035                }
6036
6037                if (hintWidth > width) {
6038                    width = hintWidth;
6039                }
6040            }
6041
6042            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6043
6044            if (mMaxWidthMode == EMS) {
6045                width = Math.min(width, mMaxWidth * getLineHeight());
6046            } else {
6047                width = Math.min(width, mMaxWidth);
6048            }
6049
6050            if (mMinWidthMode == EMS) {
6051                width = Math.max(width, mMinWidth * getLineHeight());
6052            } else {
6053                width = Math.max(width, mMinWidth);
6054            }
6055
6056            // Check against our minimum width
6057            width = Math.max(width, getSuggestedMinimumWidth());
6058
6059            if (widthMode == MeasureSpec.AT_MOST) {
6060                width = Math.min(widthSize, width);
6061            }
6062        }
6063
6064        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6065        int unpaddedWidth = want;
6066
6067        if (mHorizontallyScrolling) want = VERY_WIDE;
6068
6069        int hintWant = want;
6070        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6071
6072        if (mLayout == null) {
6073            makeNewLayout(want, hintWant, boring, hintBoring,
6074                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6075        } else {
6076            final boolean layoutChanged = (mLayout.getWidth() != want) ||
6077                    (hintWidth != hintWant) ||
6078                    (mLayout.getEllipsizedWidth() !=
6079                            width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6080
6081            final boolean widthChanged = (mHint == null) &&
6082                    (mEllipsize == null) &&
6083                    (want > mLayout.getWidth()) &&
6084                    (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6085
6086            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6087
6088            if (layoutChanged || maximumChanged) {
6089                if (!maximumChanged && widthChanged) {
6090                    mLayout.increaseWidthTo(want);
6091                } else {
6092                    makeNewLayout(want, hintWant, boring, hintBoring,
6093                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6094                }
6095            } else {
6096                // Nothing has changed
6097            }
6098        }
6099
6100        if (heightMode == MeasureSpec.EXACTLY) {
6101            // Parent has told us how big to be. So be it.
6102            height = heightSize;
6103            mDesiredHeightAtMeasure = -1;
6104        } else {
6105            int desired = getDesiredHeight();
6106
6107            height = desired;
6108            mDesiredHeightAtMeasure = desired;
6109
6110            if (heightMode == MeasureSpec.AT_MOST) {
6111                height = Math.min(desired, heightSize);
6112            }
6113        }
6114
6115        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6116        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6117            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6118        }
6119
6120        /*
6121         * We didn't let makeNewLayout() register to bring the cursor into view,
6122         * so do it here if there is any possibility that it is needed.
6123         */
6124        if (mMovement != null ||
6125            mLayout.getWidth() > unpaddedWidth ||
6126            mLayout.getHeight() > unpaddedHeight) {
6127            registerForPreDraw();
6128        } else {
6129            scrollTo(0, 0);
6130        }
6131
6132        setMeasuredDimension(width, height);
6133    }
6134
6135    private int getDesiredHeight() {
6136        return Math.max(
6137                getDesiredHeight(mLayout, true),
6138                getDesiredHeight(mHintLayout, mEllipsize != null));
6139    }
6140
6141    private int getDesiredHeight(Layout layout, boolean cap) {
6142        if (layout == null) {
6143            return 0;
6144        }
6145
6146        int linecount = layout.getLineCount();
6147        int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6148        int desired = layout.getLineTop(linecount);
6149
6150        final Drawables dr = mDrawables;
6151        if (dr != null) {
6152            desired = Math.max(desired, dr.mDrawableHeightLeft);
6153            desired = Math.max(desired, dr.mDrawableHeightRight);
6154        }
6155
6156        desired += pad;
6157
6158        if (mMaxMode == LINES) {
6159            /*
6160             * Don't cap the hint to a certain number of lines.
6161             * (Do cap it, though, if we have a maximum pixel height.)
6162             */
6163            if (cap) {
6164                if (linecount > mMaximum) {
6165                    desired = layout.getLineTop(mMaximum);
6166
6167                    if (dr != null) {
6168                        desired = Math.max(desired, dr.mDrawableHeightLeft);
6169                        desired = Math.max(desired, dr.mDrawableHeightRight);
6170                    }
6171
6172                    desired += pad;
6173                    linecount = mMaximum;
6174                }
6175            }
6176        } else {
6177            desired = Math.min(desired, mMaximum);
6178        }
6179
6180        if (mMinMode == LINES) {
6181            if (linecount < mMinimum) {
6182                desired += getLineHeight() * (mMinimum - linecount);
6183            }
6184        } else {
6185            desired = Math.max(desired, mMinimum);
6186        }
6187
6188        // Check against our minimum height
6189        desired = Math.max(desired, getSuggestedMinimumHeight());
6190
6191        return desired;
6192    }
6193
6194    /**
6195     * Check whether a change to the existing text layout requires a
6196     * new view layout.
6197     */
6198    private void checkForResize() {
6199        boolean sizeChanged = false;
6200
6201        if (mLayout != null) {
6202            // Check if our width changed
6203            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6204                sizeChanged = true;
6205                invalidate();
6206            }
6207
6208            // Check if our height changed
6209            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6210                int desiredHeight = getDesiredHeight();
6211
6212                if (desiredHeight != this.getHeight()) {
6213                    sizeChanged = true;
6214                }
6215            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6216                if (mDesiredHeightAtMeasure >= 0) {
6217                    int desiredHeight = getDesiredHeight();
6218
6219                    if (desiredHeight != mDesiredHeightAtMeasure) {
6220                        sizeChanged = true;
6221                    }
6222                }
6223            }
6224        }
6225
6226        if (sizeChanged) {
6227            requestLayout();
6228            // caller will have already invalidated
6229        }
6230    }
6231
6232    /**
6233     * Check whether entirely new text requires a new view layout
6234     * or merely a new text layout.
6235     */
6236    private void checkForRelayout() {
6237        // If we have a fixed width, we can just swap in a new text layout
6238        // if the text height stays the same or if the view height is fixed.
6239
6240        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6241                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6242                (mHint == null || mHintLayout != null) &&
6243                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6244            // Static width, so try making a new text layout.
6245
6246            int oldht = mLayout.getHeight();
6247            int want = mLayout.getWidth();
6248            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6249
6250            /*
6251             * No need to bring the text into view, since the size is not
6252             * changing (unless we do the requestLayout(), in which case it
6253             * will happen at measure).
6254             */
6255            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6256                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6257                          false);
6258
6259            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6260                // In a fixed-height view, so use our new text layout.
6261                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6262                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6263                    invalidate();
6264                    return;
6265                }
6266
6267                // Dynamic height, but height has stayed the same,
6268                // so use our new text layout.
6269                if (mLayout.getHeight() == oldht &&
6270                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6271                    invalidate();
6272                    return;
6273                }
6274            }
6275
6276            // We lose: the height has changed and we have a dynamic height.
6277            // Request a new view layout using our new text layout.
6278            requestLayout();
6279            invalidate();
6280        } else {
6281            // Dynamic width, so we have no choice but to request a new
6282            // view layout with a new text layout.
6283            nullLayouts();
6284            requestLayout();
6285            invalidate();
6286        }
6287    }
6288
6289    @Override
6290    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6291        super.onLayout(changed, left, top, right, bottom);
6292        if (changed && mEditor != null) getEditor().mTextDisplayListIsValid = false;
6293    }
6294
6295    /**
6296     * Returns true if anything changed.
6297     */
6298    private boolean bringTextIntoView() {
6299        int line = 0;
6300        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6301            line = mLayout.getLineCount() - 1;
6302        }
6303
6304        Layout.Alignment a = mLayout.getParagraphAlignment(line);
6305        int dir = mLayout.getParagraphDirection(line);
6306        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6307        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6308        int ht = mLayout.getHeight();
6309
6310        int scrollx, scrolly;
6311
6312        // Convert to left, center, or right alignment.
6313        if (a == Layout.Alignment.ALIGN_NORMAL) {
6314            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6315                Layout.Alignment.ALIGN_RIGHT;
6316        } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6317            a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6318                Layout.Alignment.ALIGN_LEFT;
6319        }
6320
6321        if (a == Layout.Alignment.ALIGN_CENTER) {
6322            /*
6323             * Keep centered if possible, or, if it is too wide to fit,
6324             * keep leading edge in view.
6325             */
6326
6327            int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6328            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6329
6330            if (right - left < hspace) {
6331                scrollx = (right + left) / 2 - hspace / 2;
6332            } else {
6333                if (dir < 0) {
6334                    scrollx = right - hspace;
6335                } else {
6336                    scrollx = left;
6337                }
6338            }
6339        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
6340            int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6341            scrollx = right - hspace;
6342        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
6343            scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6344        }
6345
6346        if (ht < vspace) {
6347            scrolly = 0;
6348        } else {
6349            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6350                scrolly = ht - vspace;
6351            } else {
6352                scrolly = 0;
6353            }
6354        }
6355
6356        if (scrollx != mScrollX || scrolly != mScrollY) {
6357            scrollTo(scrollx, scrolly);
6358            return true;
6359        } else {
6360            return false;
6361        }
6362    }
6363
6364    /**
6365     * Move the point, specified by the offset, into the view if it is needed.
6366     * This has to be called after layout. Returns true if anything changed.
6367     */
6368    public boolean bringPointIntoView(int offset) {
6369        boolean changed = false;
6370
6371        if (mLayout == null) return changed;
6372
6373        int line = mLayout.getLineForOffset(offset);
6374
6375        // FIXME: Is it okay to truncate this, or should we round?
6376        final int x = (int)mLayout.getPrimaryHorizontal(offset);
6377        final int top = mLayout.getLineTop(line);
6378        final int bottom = mLayout.getLineTop(line + 1);
6379
6380        int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6381        int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6382        int ht = mLayout.getHeight();
6383
6384        int grav;
6385
6386        switch (mLayout.getParagraphAlignment(line)) {
6387            case ALIGN_LEFT:
6388                grav = 1;
6389                break;
6390            case ALIGN_RIGHT:
6391                grav = -1;
6392                break;
6393            case ALIGN_NORMAL:
6394                grav = mLayout.getParagraphDirection(line);
6395                break;
6396            case ALIGN_OPPOSITE:
6397                grav = -mLayout.getParagraphDirection(line);
6398                break;
6399            case ALIGN_CENTER:
6400            default:
6401                grav = 0;
6402                break;
6403        }
6404
6405        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6406        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6407
6408        int hslack = (bottom - top) / 2;
6409        int vslack = hslack;
6410
6411        if (vslack > vspace / 4)
6412            vslack = vspace / 4;
6413        if (hslack > hspace / 4)
6414            hslack = hspace / 4;
6415
6416        int hs = mScrollX;
6417        int vs = mScrollY;
6418
6419        if (top - vs < vslack)
6420            vs = top - vslack;
6421        if (bottom - vs > vspace - vslack)
6422            vs = bottom - (vspace - vslack);
6423        if (ht - vs < vspace)
6424            vs = ht - vspace;
6425        if (0 - vs > 0)
6426            vs = 0;
6427
6428        if (grav != 0) {
6429            if (x - hs < hslack) {
6430                hs = x - hslack;
6431            }
6432            if (x - hs > hspace - hslack) {
6433                hs = x - (hspace - hslack);
6434            }
6435        }
6436
6437        if (grav < 0) {
6438            if (left - hs > 0)
6439                hs = left;
6440            if (right - hs < hspace)
6441                hs = right - hspace;
6442        } else if (grav > 0) {
6443            if (right - hs < hspace)
6444                hs = right - hspace;
6445            if (left - hs > 0)
6446                hs = left;
6447        } else /* grav == 0 */ {
6448            if (right - left <= hspace) {
6449                /*
6450                 * If the entire text fits, center it exactly.
6451                 */
6452                hs = left - (hspace - (right - left)) / 2;
6453            } else if (x > right - hslack) {
6454                /*
6455                 * If we are near the right edge, keep the right edge
6456                 * at the edge of the view.
6457                 */
6458                hs = right - hspace;
6459            } else if (x < left + hslack) {
6460                /*
6461                 * If we are near the left edge, keep the left edge
6462                 * at the edge of the view.
6463                 */
6464                hs = left;
6465            } else if (left > hs) {
6466                /*
6467                 * Is there whitespace visible at the left?  Fix it if so.
6468                 */
6469                hs = left;
6470            } else if (right < hs + hspace) {
6471                /*
6472                 * Is there whitespace visible at the right?  Fix it if so.
6473                 */
6474                hs = right - hspace;
6475            } else {
6476                /*
6477                 * Otherwise, float as needed.
6478                 */
6479                if (x - hs < hslack) {
6480                    hs = x - hslack;
6481                }
6482                if (x - hs > hspace - hslack) {
6483                    hs = x - (hspace - hslack);
6484                }
6485            }
6486        }
6487
6488        if (hs != mScrollX || vs != mScrollY) {
6489            if (mScroller == null) {
6490                scrollTo(hs, vs);
6491            } else {
6492                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6493                int dx = hs - mScrollX;
6494                int dy = vs - mScrollY;
6495
6496                if (duration > ANIMATED_SCROLL_GAP) {
6497                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
6498                    awakenScrollBars(mScroller.getDuration());
6499                    invalidate();
6500                } else {
6501                    if (!mScroller.isFinished()) {
6502                        mScroller.abortAnimation();
6503                    }
6504
6505                    scrollBy(dx, dy);
6506                }
6507
6508                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6509            }
6510
6511            changed = true;
6512        }
6513
6514        if (isFocused()) {
6515            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6516            // requestRectangleOnScreen() is in terms of content coordinates.
6517
6518            // The offsets here are to ensure the rectangle we are using is
6519            // within our view bounds, in case the cursor is on the far left
6520            // or right.  If it isn't withing the bounds, then this request
6521            // will be ignored.
6522            if (mTempRect == null) mTempRect = new Rect();
6523            mTempRect.set(x - 2, top, x + 2, bottom);
6524            getInterestingRect(mTempRect, line);
6525            mTempRect.offset(mScrollX, mScrollY);
6526
6527            if (requestRectangleOnScreen(mTempRect)) {
6528                changed = true;
6529            }
6530        }
6531
6532        return changed;
6533    }
6534
6535    /**
6536     * Move the cursor, if needed, so that it is at an offset that is visible
6537     * to the user.  This will not move the cursor if it represents more than
6538     * one character (a selection range).  This will only work if the
6539     * TextView contains spannable text; otherwise it will do nothing.
6540     *
6541     * @return True if the cursor was actually moved, false otherwise.
6542     */
6543    public boolean moveCursorToVisibleOffset() {
6544        if (!(mText instanceof Spannable)) {
6545            return false;
6546        }
6547        int start = getSelectionStart();
6548        int end = getSelectionEnd();
6549        if (start != end) {
6550            return false;
6551        }
6552
6553        // First: make sure the line is visible on screen:
6554
6555        int line = mLayout.getLineForOffset(start);
6556
6557        final int top = mLayout.getLineTop(line);
6558        final int bottom = mLayout.getLineTop(line + 1);
6559        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6560        int vslack = (bottom - top) / 2;
6561        if (vslack > vspace / 4)
6562            vslack = vspace / 4;
6563        final int vs = mScrollY;
6564
6565        if (top < (vs+vslack)) {
6566            line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6567        } else if (bottom > (vspace+vs-vslack)) {
6568            line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6569        }
6570
6571        // Next: make sure the character is visible on screen:
6572
6573        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6574        final int hs = mScrollX;
6575        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6576        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
6577
6578        // line might contain bidirectional text
6579        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6580        final int highChar = leftChar > rightChar ? leftChar : rightChar;
6581
6582        int newStart = start;
6583        if (newStart < lowChar) {
6584            newStart = lowChar;
6585        } else if (newStart > highChar) {
6586            newStart = highChar;
6587        }
6588
6589        if (newStart != start) {
6590            Selection.setSelection((Spannable)mText, newStart);
6591            return true;
6592        }
6593
6594        return false;
6595    }
6596
6597    @Override
6598    public void computeScroll() {
6599        if (mScroller != null) {
6600            if (mScroller.computeScrollOffset()) {
6601                mScrollX = mScroller.getCurrX();
6602                mScrollY = mScroller.getCurrY();
6603                invalidateParentCaches();
6604                postInvalidate();  // So we draw again
6605            }
6606        }
6607    }
6608
6609    private void getInterestingRect(Rect r, int line) {
6610        convertFromViewportToContentCoordinates(r);
6611
6612        // Rectangle can can be expanded on first and last line to take
6613        // padding into account.
6614        // TODO Take left/right padding into account too?
6615        if (line == 0) r.top -= getExtendedPaddingTop();
6616        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6617    }
6618
6619    private void convertFromViewportToContentCoordinates(Rect r) {
6620        final int horizontalOffset = viewportToContentHorizontalOffset();
6621        r.left += horizontalOffset;
6622        r.right += horizontalOffset;
6623
6624        final int verticalOffset = viewportToContentVerticalOffset();
6625        r.top += verticalOffset;
6626        r.bottom += verticalOffset;
6627    }
6628
6629    private int viewportToContentHorizontalOffset() {
6630        return getCompoundPaddingLeft() - mScrollX;
6631    }
6632
6633    private int viewportToContentVerticalOffset() {
6634        int offset = getExtendedPaddingTop() - mScrollY;
6635        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6636            offset += getVerticalOffset(false);
6637        }
6638        return offset;
6639    }
6640
6641    @Override
6642    public void debug(int depth) {
6643        super.debug(depth);
6644
6645        String output = debugIndent(depth);
6646        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6647                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6648                + "} ";
6649
6650        if (mText != null) {
6651
6652            output += "mText=\"" + mText + "\" ";
6653            if (mLayout != null) {
6654                output += "mLayout width=" + mLayout.getWidth()
6655                        + " height=" + mLayout.getHeight();
6656            }
6657        } else {
6658            output += "mText=NULL";
6659        }
6660        Log.d(VIEW_LOG_TAG, output);
6661    }
6662
6663    /**
6664     * Convenience for {@link Selection#getSelectionStart}.
6665     */
6666    @ViewDebug.ExportedProperty(category = "text")
6667    public int getSelectionStart() {
6668        return Selection.getSelectionStart(getText());
6669    }
6670
6671    /**
6672     * Convenience for {@link Selection#getSelectionEnd}.
6673     */
6674    @ViewDebug.ExportedProperty(category = "text")
6675    public int getSelectionEnd() {
6676        return Selection.getSelectionEnd(getText());
6677    }
6678
6679    /**
6680     * Return true iff there is a selection inside this text view.
6681     */
6682    public boolean hasSelection() {
6683        final int selectionStart = getSelectionStart();
6684        final int selectionEnd = getSelectionEnd();
6685
6686        return selectionStart >= 0 && selectionStart != selectionEnd;
6687    }
6688
6689    /**
6690     * Sets the properties of this field (lines, horizontally scrolling,
6691     * transformation method) to be for a single-line input.
6692     *
6693     * @attr ref android.R.styleable#TextView_singleLine
6694     */
6695    public void setSingleLine() {
6696        setSingleLine(true);
6697    }
6698
6699    /**
6700     * Sets the properties of this field to transform input to ALL CAPS
6701     * display. This may use a "small caps" formatting if available.
6702     * This setting will be ignored if this field is editable or selectable.
6703     *
6704     * This call replaces the current transformation method. Disabling this
6705     * will not necessarily restore the previous behavior from before this
6706     * was enabled.
6707     *
6708     * @see #setTransformationMethod(TransformationMethod)
6709     * @attr ref android.R.styleable#TextView_textAllCaps
6710     */
6711    public void setAllCaps(boolean allCaps) {
6712        if (allCaps) {
6713            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
6714        } else {
6715            setTransformationMethod(null);
6716        }
6717    }
6718
6719    /**
6720     * If true, sets the properties of this field (number of lines, horizontally scrolling,
6721     * transformation method) to be for a single-line input; if false, restores these to the default
6722     * conditions.
6723     *
6724     * Note that the default conditions are not necessarily those that were in effect prior this
6725     * method, and you may want to reset these properties to your custom values.
6726     *
6727     * @attr ref android.R.styleable#TextView_singleLine
6728     */
6729    @android.view.RemotableViewMethod
6730    public void setSingleLine(boolean singleLine) {
6731        // Could be used, but may break backward compatibility.
6732        // if (mSingleLine == singleLine) return;
6733        setInputTypeSingleLine(singleLine);
6734        applySingleLine(singleLine, true, true);
6735    }
6736
6737    /**
6738     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
6739     * @param singleLine
6740     */
6741    private void setInputTypeSingleLine(boolean singleLine) {
6742        if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6743            if (singleLine) {
6744                getEditor().mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6745            } else {
6746                getEditor().mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6747            }
6748        }
6749    }
6750
6751    private void applySingleLine(boolean singleLine, boolean applyTransformation,
6752            boolean changeMaxLines) {
6753        mSingleLine = singleLine;
6754        if (singleLine) {
6755            setLines(1);
6756            setHorizontallyScrolling(true);
6757            if (applyTransformation) {
6758                setTransformationMethod(SingleLineTransformationMethod.getInstance());
6759            }
6760        } else {
6761            if (changeMaxLines) {
6762                setMaxLines(Integer.MAX_VALUE);
6763            }
6764            setHorizontallyScrolling(false);
6765            if (applyTransformation) {
6766                setTransformationMethod(null);
6767            }
6768        }
6769    }
6770
6771    /**
6772     * Causes words in the text that are longer than the view is wide
6773     * to be ellipsized instead of broken in the middle.  You may also
6774     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
6775     * to constrain the text to a single line.  Use <code>null</code>
6776     * to turn off ellipsizing.
6777     *
6778     * If {@link #setMaxLines} has been used to set two or more lines,
6779     * {@link android.text.TextUtils.TruncateAt#END} and
6780     * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
6781     * (other ellipsizing types will not do anything).
6782     *
6783     * @attr ref android.R.styleable#TextView_ellipsize
6784     */
6785    public void setEllipsize(TextUtils.TruncateAt where) {
6786        // TruncateAt is an enum. != comparison is ok between these singleton objects.
6787        if (mEllipsize != where) {
6788            mEllipsize = where;
6789
6790            if (mLayout != null) {
6791                nullLayouts();
6792                requestLayout();
6793                invalidate();
6794            }
6795        }
6796    }
6797
6798    /**
6799     * Sets how many times to repeat the marquee animation. Only applied if the
6800     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
6801     *
6802     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
6803     */
6804    public void setMarqueeRepeatLimit(int marqueeLimit) {
6805        mMarqueeRepeatLimit = marqueeLimit;
6806    }
6807
6808    /**
6809     * Returns where, if anywhere, words that are longer than the view
6810     * is wide should be ellipsized.
6811     */
6812    @ViewDebug.ExportedProperty
6813    public TextUtils.TruncateAt getEllipsize() {
6814        return mEllipsize;
6815    }
6816
6817    /**
6818     * Set the TextView so that when it takes focus, all the text is
6819     * selected.
6820     *
6821     * @attr ref android.R.styleable#TextView_selectAllOnFocus
6822     */
6823    @android.view.RemotableViewMethod
6824    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
6825        createEditorIfNeeded("setSelectAllOnFocus");
6826        getEditor().mSelectAllOnFocus = selectAllOnFocus;
6827
6828        if (selectAllOnFocus && !(mText instanceof Spannable)) {
6829            setText(mText, BufferType.SPANNABLE);
6830        }
6831    }
6832
6833    /**
6834     * Set whether the cursor is visible.  The default is true.
6835     *
6836     * @attr ref android.R.styleable#TextView_cursorVisible
6837     */
6838    @android.view.RemotableViewMethod
6839    public void setCursorVisible(boolean visible) {
6840        if (visible && mEditor == null) return; // visible is the default value with no edit data
6841        createEditorIfNeeded("setCursorVisible");
6842        if (getEditor().mCursorVisible != visible) {
6843            getEditor().mCursorVisible = visible;
6844            invalidate();
6845
6846            makeBlink();
6847
6848            // InsertionPointCursorController depends on mCursorVisible
6849            prepareCursorControllers();
6850        }
6851    }
6852
6853    private boolean isCursorVisible() {
6854        // The default value is true, even when there is no associated Editor
6855        return mEditor == null ? true : (getEditor().mCursorVisible && isTextEditable());
6856    }
6857
6858    private boolean canMarquee() {
6859        int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
6860        return width > 0 && (mLayout.getLineWidth(0) > width ||
6861                (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
6862                        mSavedMarqueeModeLayout.getLineWidth(0) > width));
6863    }
6864
6865    private void startMarquee() {
6866        // Do not ellipsize EditText
6867        if (getKeyListener() != null) return;
6868
6869        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
6870            return;
6871        }
6872
6873        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
6874                getLineCount() == 1 && canMarquee()) {
6875
6876            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6877                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
6878                final Layout tmp = mLayout;
6879                mLayout = mSavedMarqueeModeLayout;
6880                mSavedMarqueeModeLayout = tmp;
6881                setHorizontalFadingEdgeEnabled(true);
6882                requestLayout();
6883                invalidate();
6884            }
6885
6886            if (mMarquee == null) mMarquee = new Marquee(this);
6887            mMarquee.start(mMarqueeRepeatLimit);
6888        }
6889    }
6890
6891    private void stopMarquee() {
6892        if (mMarquee != null && !mMarquee.isStopped()) {
6893            mMarquee.stop();
6894        }
6895
6896        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
6897            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
6898            final Layout tmp = mSavedMarqueeModeLayout;
6899            mSavedMarqueeModeLayout = mLayout;
6900            mLayout = tmp;
6901            setHorizontalFadingEdgeEnabled(false);
6902            requestLayout();
6903            invalidate();
6904        }
6905    }
6906
6907    private void startStopMarquee(boolean start) {
6908        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6909            if (start) {
6910                startMarquee();
6911            } else {
6912                stopMarquee();
6913            }
6914        }
6915    }
6916
6917    /**
6918     * This method is called when the text is changed, in case any subclasses
6919     * would like to know.
6920     *
6921     * Within <code>text</code>, the <code>lengthAfter</code> characters
6922     * beginning at <code>start</code> have just replaced old text that had
6923     * length <code>lengthBefore</code>. It is an error to attempt to make
6924     * changes to <code>text</code> from this callback.
6925     *
6926     * @param text The text the TextView is displaying
6927     * @param start The offset of the start of the range of the text that was
6928     * modified
6929     * @param lengthBefore The length of the former text that has been replaced
6930     * @param lengthAfter The length of the replacement modified text
6931     */
6932    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
6933        // intentionally empty, template pattern method can be overridden by subclasses
6934    }
6935
6936    /**
6937     * This method is called when the selection has changed, in case any
6938     * subclasses would like to know.
6939     *
6940     * @param selStart The new selection start location.
6941     * @param selEnd The new selection end location.
6942     */
6943    protected void onSelectionChanged(int selStart, int selEnd) {
6944        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
6945        if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
6946    }
6947
6948    /**
6949     * Adds a TextWatcher to the list of those whose methods are called
6950     * whenever this TextView's text changes.
6951     * <p>
6952     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6953     * not called after {@link #setText} calls.  Now, doing {@link #setText}
6954     * if there are any text changed listeners forces the buffer type to
6955     * Editable if it would not otherwise be and does call this method.
6956     */
6957    public void addTextChangedListener(TextWatcher watcher) {
6958        if (mListeners == null) {
6959            mListeners = new ArrayList<TextWatcher>();
6960        }
6961
6962        mListeners.add(watcher);
6963    }
6964
6965    /**
6966     * Removes the specified TextWatcher from the list of those whose
6967     * methods are called
6968     * whenever this TextView's text changes.
6969     */
6970    public void removeTextChangedListener(TextWatcher watcher) {
6971        if (mListeners != null) {
6972            int i = mListeners.indexOf(watcher);
6973
6974            if (i >= 0) {
6975                mListeners.remove(i);
6976            }
6977        }
6978    }
6979
6980    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
6981        if (mListeners != null) {
6982            final ArrayList<TextWatcher> list = mListeners;
6983            final int count = list.size();
6984            for (int i = 0; i < count; i++) {
6985                list.get(i).beforeTextChanged(text, start, before, after);
6986            }
6987        }
6988
6989        // The spans that are inside or intersect the modified region no longer make sense
6990        removeIntersectingSpans(start, start + before, SpellCheckSpan.class);
6991        removeIntersectingSpans(start, start + before, SuggestionSpan.class);
6992    }
6993
6994    // Removes all spans that are inside or actually overlap the start..end range
6995    private <T> void removeIntersectingSpans(int start, int end, Class<T> type) {
6996        if (!(mText instanceof Editable)) return;
6997        Editable text = (Editable) mText;
6998
6999        T[] spans = text.getSpans(start, end, type);
7000        final int length = spans.length;
7001        for (int i = 0; i < length; i++) {
7002            final int s = text.getSpanStart(spans[i]);
7003            final int e = text.getSpanEnd(spans[i]);
7004            // Spans that are adjacent to the edited region will be handled in
7005            // updateSpellCheckSpans. Result depends on what will be added (space or text)
7006            if (e == start || s == end) break;
7007            text.removeSpan(spans[i]);
7008        }
7009    }
7010
7011    /**
7012     * Not private so it can be called from an inner class without going
7013     * through a thunk.
7014     */
7015    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7016        if (mListeners != null) {
7017            final ArrayList<TextWatcher> list = mListeners;
7018            final int count = list.size();
7019            for (int i = 0; i < count; i++) {
7020                list.get(i).onTextChanged(text, start, before, after);
7021            }
7022        }
7023
7024        if (mEditor != null) getEditor().sendOnTextChanged(start, after);
7025    }
7026
7027    /**
7028     * Not private so it can be called from an inner class without going
7029     * through a thunk.
7030     */
7031    void sendAfterTextChanged(Editable text) {
7032        if (mListeners != null) {
7033            final ArrayList<TextWatcher> list = mListeners;
7034            final int count = list.size();
7035            for (int i = 0; i < count; i++) {
7036                list.get(i).afterTextChanged(text);
7037            }
7038        }
7039    }
7040
7041    /**
7042     * Not private so it can be called from an inner class without going
7043     * through a thunk.
7044     */
7045    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
7046        final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState;
7047        if (ims == null || ims.mBatchEditNesting == 0) {
7048            updateAfterEdit();
7049        }
7050        if (ims != null) {
7051            ims.mContentChanged = true;
7052            if (ims.mChangedStart < 0) {
7053                ims.mChangedStart = start;
7054                ims.mChangedEnd = start+before;
7055            } else {
7056                ims.mChangedStart = Math.min(ims.mChangedStart, start);
7057                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
7058            }
7059            ims.mChangedDelta += after-before;
7060        }
7061
7062        sendOnTextChanged(buffer, start, before, after);
7063        onTextChanged(buffer, start, before, after);
7064    }
7065
7066    /**
7067     * Not private so it can be called from an inner class without going
7068     * through a thunk.
7069     */
7070    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
7071        // XXX Make the start and end move together if this ends up
7072        // spending too much time invalidating.
7073
7074        boolean selChanged = false;
7075        int newSelStart=-1, newSelEnd=-1;
7076
7077        final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState;
7078
7079        if (what == Selection.SELECTION_END) {
7080            selChanged = true;
7081            newSelEnd = newStart;
7082
7083            if (oldStart >= 0 || newStart >= 0) {
7084                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
7085                registerForPreDraw();
7086                makeBlink();
7087            }
7088        }
7089
7090        if (what == Selection.SELECTION_START) {
7091            selChanged = true;
7092            newSelStart = newStart;
7093
7094            if (oldStart >= 0 || newStart >= 0) {
7095                int end = Selection.getSelectionEnd(buf);
7096                invalidateCursor(end, oldStart, newStart);
7097            }
7098        }
7099
7100        if (selChanged) {
7101            mHighlightPathBogus = true;
7102            if (mEditor != null && !isFocused()) getEditor().mSelectionMoved = true;
7103
7104            if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7105                if (newSelStart < 0) {
7106                    newSelStart = Selection.getSelectionStart(buf);
7107                }
7108                if (newSelEnd < 0) {
7109                    newSelEnd = Selection.getSelectionEnd(buf);
7110                }
7111                onSelectionChanged(newSelStart, newSelEnd);
7112            }
7113        }
7114
7115        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7116                what instanceof CharacterStyle) {
7117            if (ims == null || ims.mBatchEditNesting == 0) {
7118                invalidate();
7119                mHighlightPathBogus = true;
7120                checkForResize();
7121            } else {
7122                ims.mContentChanged = true;
7123            }
7124            if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
7125        }
7126
7127        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7128            mHighlightPathBogus = true;
7129            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7130                ims.mSelectionModeChanged = true;
7131            }
7132
7133            if (Selection.getSelectionStart(buf) >= 0) {
7134                if (ims == null || ims.mBatchEditNesting == 0) {
7135                    invalidateCursor();
7136                } else {
7137                    ims.mCursorChanged = true;
7138                }
7139            }
7140        }
7141
7142        if (what instanceof ParcelableSpan) {
7143            // If this is a span that can be sent to a remote process,
7144            // the current extract editor would be interested in it.
7145            if (ims != null && ims.mExtracting != null) {
7146                if (ims.mBatchEditNesting != 0) {
7147                    if (oldStart >= 0) {
7148                        if (ims.mChangedStart > oldStart) {
7149                            ims.mChangedStart = oldStart;
7150                        }
7151                        if (ims.mChangedStart > oldEnd) {
7152                            ims.mChangedStart = oldEnd;
7153                        }
7154                    }
7155                    if (newStart >= 0) {
7156                        if (ims.mChangedStart > newStart) {
7157                            ims.mChangedStart = newStart;
7158                        }
7159                        if (ims.mChangedStart > newEnd) {
7160                            ims.mChangedStart = newEnd;
7161                        }
7162                    }
7163                } else {
7164                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7165                            + oldStart + "-" + oldEnd + ","
7166                            + newStart + "-" + newEnd + what);
7167                    ims.mContentChanged = true;
7168                }
7169            }
7170        }
7171
7172        if (mEditor != null && getEditor().mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
7173            getEditor().mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
7174        }
7175    }
7176
7177    /**
7178     * Create new SpellCheckSpans on the modified region.
7179     */
7180    private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) {
7181        if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) {
7182            if (getEditor().mSpellChecker == null && createSpellChecker) {
7183                getEditor().mSpellChecker = new SpellChecker(this);
7184            }
7185            if (getEditor().mSpellChecker != null) {
7186                getEditor().mSpellChecker.spellCheck(start, end);
7187            }
7188        }
7189    }
7190
7191    /**
7192     * @hide
7193     */
7194    @Override
7195    public void dispatchFinishTemporaryDetach() {
7196        mDispatchTemporaryDetach = true;
7197        super.dispatchFinishTemporaryDetach();
7198        mDispatchTemporaryDetach = false;
7199    }
7200
7201    @Override
7202    public void onStartTemporaryDetach() {
7203        super.onStartTemporaryDetach();
7204        // Only track when onStartTemporaryDetach() is called directly,
7205        // usually because this instance is an editable field in a list
7206        if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
7207
7208        // Because of View recycling in ListView, there is no easy way to know when a TextView with
7209        // selection becomes visible again. Until a better solution is found, stop text selection
7210        // mode (if any) as soon as this TextView is recycled.
7211        if (mEditor != null) hideControllers();
7212    }
7213
7214    @Override
7215    public void onFinishTemporaryDetach() {
7216        super.onFinishTemporaryDetach();
7217        // Only track when onStartTemporaryDetach() is called directly,
7218        // usually because this instance is an editable field in a list
7219        if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
7220    }
7221
7222    @Override
7223    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7224        if (mTemporaryDetach) {
7225            // If we are temporarily in the detach state, then do nothing.
7226            super.onFocusChanged(focused, direction, previouslyFocusedRect);
7227            return;
7228        }
7229
7230        if (mEditor != null) getEditor().onFocusChanged(focused, direction);
7231
7232        if (focused) {
7233            if (mText instanceof Spannable) {
7234                Spannable sp = (Spannable) mText;
7235                MetaKeyKeyListener.resetMetaState(sp);
7236            }
7237        }
7238
7239        startStopMarquee(focused);
7240
7241        if (mTransformation != null) {
7242            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
7243        }
7244
7245        super.onFocusChanged(focused, direction, previouslyFocusedRect);
7246    }
7247
7248    @Override
7249    public void onWindowFocusChanged(boolean hasWindowFocus) {
7250        super.onWindowFocusChanged(hasWindowFocus);
7251
7252        if (mEditor != null) getEditor().onWindowFocusChanged(hasWindowFocus);
7253
7254        startStopMarquee(hasWindowFocus);
7255    }
7256
7257    @Override
7258    protected void onVisibilityChanged(View changedView, int visibility) {
7259        super.onVisibilityChanged(changedView, visibility);
7260        if (mEditor != null && visibility != VISIBLE) {
7261            hideControllers();
7262        }
7263    }
7264
7265    /**
7266     * Use {@link BaseInputConnection#removeComposingSpans
7267     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7268     * state from this text view.
7269     */
7270    public void clearComposingText() {
7271        if (mText instanceof Spannable) {
7272            BaseInputConnection.removeComposingSpans((Spannable)mText);
7273        }
7274    }
7275
7276    @Override
7277    public void setSelected(boolean selected) {
7278        boolean wasSelected = isSelected();
7279
7280        super.setSelected(selected);
7281
7282        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7283            if (selected) {
7284                startMarquee();
7285            } else {
7286                stopMarquee();
7287            }
7288        }
7289    }
7290
7291    @Override
7292    public boolean onTouchEvent(MotionEvent event) {
7293        final int action = event.getActionMasked();
7294
7295        if (mEditor != null) getEditor().onTouchEvent(event);
7296
7297        final boolean superResult = super.onTouchEvent(event);
7298
7299        /*
7300         * Don't handle the release after a long press, because it will
7301         * move the selection away from whatever the menu action was
7302         * trying to affect.
7303         */
7304        if (mEditor != null && getEditor().mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7305            getEditor().mDiscardNextActionUp = false;
7306            return superResult;
7307        }
7308
7309        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
7310                (mEditor == null || !getEditor().mIgnoreActionUpEvent) && isFocused();
7311
7312         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7313                && mText instanceof Spannable && mLayout != null) {
7314            boolean handled = false;
7315
7316            if (mMovement != null) {
7317                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7318            }
7319
7320            final boolean textIsSelectable = isTextSelectable();
7321            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
7322                // The LinkMovementMethod which should handle taps on links has not been installed
7323                // on non editable text that support text selection.
7324                // We reproduce its behavior here to open links for these.
7325                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7326                        getSelectionEnd(), ClickableSpan.class);
7327
7328                if (links.length > 0) {
7329                    links[0].onClick(this);
7330                    handled = true;
7331                }
7332            }
7333
7334            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
7335                // Show the IME, except when selecting in read-only text.
7336                final InputMethodManager imm = InputMethodManager.peekInstance();
7337                viewClicked(imm);
7338                if (!textIsSelectable) {
7339                    handled |= imm != null && imm.showSoftInput(this, 0);
7340                }
7341
7342                boolean selectAllGotFocus = getEditor().mSelectAllOnFocus && didTouchFocusSelect();
7343                hideControllers();
7344                if (!selectAllGotFocus && mText.length() > 0) {
7345                    // Move cursor
7346                    final int offset = getOffsetForPosition(event.getX(), event.getY());
7347                    Selection.setSelection((Spannable) mText, offset);
7348                    if (getEditor().mSpellChecker != null) {
7349                        // When the cursor moves, the word that was typed may need spell check
7350                        getEditor().mSpellChecker.onSelectionChanged();
7351                    }
7352                    if (!extractedTextModeWillBeStarted()) {
7353                        if (isCursorInsideEasyCorrectionSpan()) {
7354                            getEditor().mShowSuggestionRunnable = new Runnable() {
7355                                public void run() {
7356                                    showSuggestions();
7357                                }
7358                            };
7359                            // removeCallbacks is performed on every touch
7360                            postDelayed(getEditor().mShowSuggestionRunnable,
7361                                    ViewConfiguration.getDoubleTapTimeout());
7362                        } else if (hasInsertionController()) {
7363                            getInsertionController().show();
7364                        }
7365                    }
7366                }
7367
7368                handled = true;
7369            }
7370
7371            if (handled) {
7372                return true;
7373            }
7374        }
7375
7376        return superResult;
7377    }
7378
7379    /**
7380     * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}.
7381     */
7382    private boolean isCursorInsideSuggestionSpan() {
7383        if (!(mText instanceof Spannable)) return false;
7384
7385        SuggestionSpan[] suggestionSpans = ((Spannable) mText).getSpans(getSelectionStart(),
7386                getSelectionEnd(), SuggestionSpan.class);
7387        return (suggestionSpans.length > 0);
7388    }
7389
7390    /**
7391     * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with
7392     * {@link SuggestionSpan#FLAG_EASY_CORRECT} set.
7393     */
7394    private boolean isCursorInsideEasyCorrectionSpan() {
7395        Spannable spannable = (Spannable) mText;
7396        SuggestionSpan[] suggestionSpans = spannable.getSpans(getSelectionStart(),
7397                getSelectionEnd(), SuggestionSpan.class);
7398        for (int i = 0; i < suggestionSpans.length; i++) {
7399            if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) {
7400                return true;
7401            }
7402        }
7403        return false;
7404    }
7405
7406    /**
7407     * Downgrades to simple suggestions all the easy correction spans that are not a spell check
7408     * span.
7409     */
7410    private void downgradeEasyCorrectionSpans() {
7411        if (mText instanceof Spannable) {
7412            Spannable spannable = (Spannable) mText;
7413            SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
7414                    spannable.length(), SuggestionSpan.class);
7415            for (int i = 0; i < suggestionSpans.length; i++) {
7416                int flags = suggestionSpans[i].getFlags();
7417                if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
7418                        && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) {
7419                    flags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
7420                    suggestionSpans[i].setFlags(flags);
7421                }
7422            }
7423        }
7424    }
7425
7426    @Override
7427    public boolean onGenericMotionEvent(MotionEvent event) {
7428        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7429            try {
7430                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7431                    return true;
7432                }
7433            } catch (AbstractMethodError ex) {
7434                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7435                // Ignore its absence in case third party applications implemented the
7436                // interface directly.
7437            }
7438        }
7439        return super.onGenericMotionEvent(event);
7440    }
7441
7442    private void prepareCursorControllers() {
7443        if (mEditor == null) return;
7444
7445        boolean windowSupportsHandles = false;
7446
7447        ViewGroup.LayoutParams params = getRootView().getLayoutParams();
7448        if (params instanceof WindowManager.LayoutParams) {
7449            WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
7450            windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
7451                    || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
7452        }
7453
7454        getEditor().mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
7455        getEditor().mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
7456                mLayout != null;
7457
7458        if (!getEditor().mInsertionControllerEnabled) {
7459            hideInsertionPointCursorController();
7460            if (getEditor().mInsertionPointCursorController != null) {
7461                getEditor().mInsertionPointCursorController.onDetached();
7462                getEditor().mInsertionPointCursorController = null;
7463            }
7464        }
7465
7466        if (!getEditor().mSelectionControllerEnabled) {
7467            stopSelectionActionMode();
7468            if (getEditor().mSelectionModifierCursorController != null) {
7469                getEditor().mSelectionModifierCursorController.onDetached();
7470                getEditor().mSelectionModifierCursorController = null;
7471            }
7472        }
7473    }
7474
7475    /**
7476     * @return True iff this TextView contains a text that can be edited, or if this is
7477     * a selectable TextView.
7478     */
7479    private boolean isTextEditable() {
7480        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
7481    }
7482
7483    /**
7484     * Returns true, only while processing a touch gesture, if the initial
7485     * touch down event caused focus to move to the text view and as a result
7486     * its selection changed.  Only valid while processing the touch gesture
7487     * of interest.
7488     */
7489    public boolean didTouchFocusSelect() {
7490        return mEditor != null && getEditor().mTouchFocusSelected;
7491    }
7492
7493    @Override
7494    public void cancelLongPress() {
7495        super.cancelLongPress();
7496        if (mEditor != null) getEditor().mIgnoreActionUpEvent = true;
7497    }
7498
7499    @Override
7500    public boolean onTrackballEvent(MotionEvent event) {
7501        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7502            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7503                return true;
7504            }
7505        }
7506
7507        return super.onTrackballEvent(event);
7508    }
7509
7510    public void setScroller(Scroller s) {
7511        mScroller = s;
7512    }
7513
7514    /**
7515     * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
7516     */
7517    private boolean shouldBlink() {
7518        if (mEditor == null || !isCursorVisible() || !isFocused()) return false;
7519
7520        final int start = getSelectionStart();
7521        if (start < 0) return false;
7522
7523        final int end = getSelectionEnd();
7524        if (end < 0) return false;
7525
7526        return start == end;
7527    }
7528
7529    private void makeBlink() {
7530        if (shouldBlink()) {
7531            getEditor().mShowCursor = SystemClock.uptimeMillis();
7532            if (getEditor().mBlink == null) getEditor().mBlink = new Blink(this);
7533            getEditor().mBlink.removeCallbacks(getEditor().mBlink);
7534            getEditor().mBlink.postAtTime(getEditor().mBlink, getEditor().mShowCursor + BLINK);
7535        } else {
7536            if (mEditor != null && getEditor().mBlink != null) getEditor().mBlink.removeCallbacks(getEditor().mBlink);
7537        }
7538    }
7539
7540    @Override
7541    protected float getLeftFadingEdgeStrength() {
7542        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7543        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7544                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7545            if (mMarquee != null && !mMarquee.isStopped()) {
7546                final Marquee marquee = mMarquee;
7547                if (marquee.shouldDrawLeftFade()) {
7548                    return marquee.mScroll / getHorizontalFadingEdgeLength();
7549                } else {
7550                    return 0.0f;
7551                }
7552            } else if (getLineCount() == 1) {
7553                final int layoutDirection = getResolvedLayoutDirection();
7554                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7555                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7556                    case Gravity.LEFT:
7557                        return 0.0f;
7558                    case Gravity.RIGHT:
7559                        return (mLayout.getLineRight(0) - (mRight - mLeft) -
7560                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
7561                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7562                    case Gravity.CENTER_HORIZONTAL:
7563                        return 0.0f;
7564                }
7565            }
7566        }
7567        return super.getLeftFadingEdgeStrength();
7568    }
7569
7570    @Override
7571    protected float getRightFadingEdgeStrength() {
7572        if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
7573        if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7574                mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7575            if (mMarquee != null && !mMarquee.isStopped()) {
7576                final Marquee marquee = mMarquee;
7577                return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
7578            } else if (getLineCount() == 1) {
7579                final int layoutDirection = getResolvedLayoutDirection();
7580                final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7581                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7582                    case Gravity.LEFT:
7583                        final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7584                                getCompoundPaddingRight();
7585                        final float lineWidth = mLayout.getLineWidth(0);
7586                        return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
7587                    case Gravity.RIGHT:
7588                        return 0.0f;
7589                    case Gravity.CENTER_HORIZONTAL:
7590                    case Gravity.FILL_HORIZONTAL:
7591                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7592                                getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7593                                getHorizontalFadingEdgeLength();
7594                }
7595            }
7596        }
7597        return super.getRightFadingEdgeStrength();
7598    }
7599
7600    @Override
7601    protected int computeHorizontalScrollRange() {
7602        if (mLayout != null) {
7603            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7604                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7605        }
7606
7607        return super.computeHorizontalScrollRange();
7608    }
7609
7610    @Override
7611    protected int computeVerticalScrollRange() {
7612        if (mLayout != null)
7613            return mLayout.getHeight();
7614
7615        return super.computeVerticalScrollRange();
7616    }
7617
7618    @Override
7619    protected int computeVerticalScrollExtent() {
7620        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7621    }
7622
7623    @Override
7624    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7625        super.findViewsWithText(outViews, searched, flags);
7626        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7627                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7628            String searchedLowerCase = searched.toString().toLowerCase();
7629            String textLowerCase = mText.toString().toLowerCase();
7630            if (textLowerCase.contains(searchedLowerCase)) {
7631                outViews.add(this);
7632            }
7633        }
7634    }
7635
7636    public enum BufferType {
7637        NORMAL, SPANNABLE, EDITABLE,
7638    }
7639
7640    /**
7641     * Returns the TextView_textColor attribute from the
7642     * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7643     * from the TextView_textAppearance attribute, if TextView_textColor
7644     * was not set directly.
7645     */
7646    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7647        ColorStateList colors;
7648        colors = attrs.getColorStateList(com.android.internal.R.styleable.
7649                                         TextView_textColor);
7650
7651        if (colors == null) {
7652            int ap = attrs.getResourceId(com.android.internal.R.styleable.
7653                                         TextView_textAppearance, -1);
7654            if (ap != -1) {
7655                TypedArray appearance;
7656                appearance = context.obtainStyledAttributes(ap,
7657                                            com.android.internal.R.styleable.TextAppearance);
7658                colors = appearance.getColorStateList(com.android.internal.R.styleable.
7659                                                  TextAppearance_textColor);
7660                appearance.recycle();
7661            }
7662        }
7663
7664        return colors;
7665    }
7666
7667    /**
7668     * Returns the default color from the TextView_textColor attribute
7669     * from the AttributeSet, if set, or the default color from the
7670     * TextAppearance_textColor from the TextView_textAppearance attribute,
7671     * if TextView_textColor was not set directly.
7672     */
7673    public static int getTextColor(Context context,
7674                                   TypedArray attrs,
7675                                   int def) {
7676        ColorStateList colors = getTextColors(context, attrs);
7677
7678        if (colors == null) {
7679            return def;
7680        } else {
7681            return colors.getDefaultColor();
7682        }
7683    }
7684
7685    @Override
7686    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
7687        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7688        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7689            switch (keyCode) {
7690            case KeyEvent.KEYCODE_A:
7691                if (canSelectText()) {
7692                    return onTextContextMenuItem(ID_SELECT_ALL);
7693                }
7694                break;
7695            case KeyEvent.KEYCODE_X:
7696                if (canCut()) {
7697                    return onTextContextMenuItem(ID_CUT);
7698                }
7699                break;
7700            case KeyEvent.KEYCODE_C:
7701                if (canCopy()) {
7702                    return onTextContextMenuItem(ID_COPY);
7703                }
7704                break;
7705            case KeyEvent.KEYCODE_V:
7706                if (canPaste()) {
7707                    return onTextContextMenuItem(ID_PASTE);
7708                }
7709                break;
7710            }
7711        }
7712        return super.onKeyShortcut(keyCode, event);
7713    }
7714
7715    /**
7716     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7717     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
7718     * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient.
7719     */
7720    private boolean canSelectText() {
7721        return hasSelectionController() && mText.length() != 0;
7722    }
7723
7724    /**
7725     * Test based on the <i>intrinsic</i> charateristics of the TextView.
7726     * The text must be spannable and the movement method must allow for arbitary selection.
7727     *
7728     * See also {@link #canSelectText()}.
7729     */
7730    private boolean textCanBeSelected() {
7731        // prepareCursorController() relies on this method.
7732        // If you change this condition, make sure prepareCursorController is called anywhere
7733        // the value of this condition might be changed.
7734        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
7735        return isTextEditable() || (isTextSelectable() && mText instanceof Spannable && isEnabled());
7736    }
7737
7738    private boolean canCut() {
7739        if (hasPasswordTransformationMethod()) {
7740            return false;
7741        }
7742
7743        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && getEditor().mKeyListener != null) {
7744            return true;
7745        }
7746
7747        return false;
7748    }
7749
7750    private boolean canCopy() {
7751        if (hasPasswordTransformationMethod()) {
7752            return false;
7753        }
7754
7755        if (mText.length() > 0 && hasSelection()) {
7756            return true;
7757        }
7758
7759        return false;
7760    }
7761
7762    private boolean canPaste() {
7763        return (mText instanceof Editable &&
7764                mEditor != null && getEditor().mKeyListener != null &&
7765                getSelectionStart() >= 0 &&
7766                getSelectionEnd() >= 0 &&
7767                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
7768                hasPrimaryClip());
7769    }
7770
7771    private static long packRangeInLong(int start, int end) {
7772        return (((long) start) << 32) | end;
7773    }
7774
7775    private static int extractRangeStartFromLong(long range) {
7776        return (int) (range >>> 32);
7777    }
7778
7779    private static int extractRangeEndFromLong(long range) {
7780        return (int) (range & 0x00000000FFFFFFFFL);
7781    }
7782
7783    private boolean selectAll() {
7784        final int length = mText.length();
7785        Selection.setSelection((Spannable) mText, 0, length);
7786        return length > 0;
7787    }
7788
7789    /**
7790     * Adjusts selection to the word under last touch offset.
7791     * Return true if the operation was successfully performed.
7792     */
7793    private boolean selectCurrentWord() {
7794        if (!canSelectText()) {
7795            return false;
7796        }
7797
7798        if (hasPasswordTransformationMethod()) {
7799            // Always select all on a password field.
7800            // Cut/copy menu entries are not available for passwords, but being able to select all
7801            // is however useful to delete or paste to replace the entire content.
7802            return selectAll();
7803        }
7804
7805        int inputType = getInputType();
7806        int klass = inputType & InputType.TYPE_MASK_CLASS;
7807        int variation = inputType & InputType.TYPE_MASK_VARIATION;
7808
7809        // Specific text field types: select the entire text for these
7810        if (klass == InputType.TYPE_CLASS_NUMBER ||
7811                klass == InputType.TYPE_CLASS_PHONE ||
7812                klass == InputType.TYPE_CLASS_DATETIME ||
7813                variation == InputType.TYPE_TEXT_VARIATION_URI ||
7814                variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
7815                variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
7816                variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
7817            return selectAll();
7818        }
7819
7820        long lastTouchOffsets = getLastTouchOffsets();
7821        final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
7822        final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
7823
7824        // Safety check in case standard touch event handling has been bypassed
7825        if (minOffset < 0 || minOffset >= mText.length()) return false;
7826        if (maxOffset < 0 || maxOffset >= mText.length()) return false;
7827
7828        int selectionStart, selectionEnd;
7829
7830        // If a URLSpan (web address, email, phone...) is found at that position, select it.
7831        URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
7832        if (urlSpans.length >= 1) {
7833            URLSpan urlSpan = urlSpans[0];
7834            selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
7835            selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
7836        } else {
7837            final WordIterator wordIterator = getWordIterator();
7838            wordIterator.setCharSequence(mText, minOffset, maxOffset);
7839
7840            selectionStart = wordIterator.getBeginning(minOffset);
7841            selectionEnd = wordIterator.getEnd(maxOffset);
7842
7843            if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE ||
7844                    selectionStart == selectionEnd) {
7845                // Possible when the word iterator does not properly handle the text's language
7846                long range = getCharRange(minOffset);
7847                selectionStart = extractRangeStartFromLong(range);
7848                selectionEnd = extractRangeEndFromLong(range);
7849            }
7850        }
7851
7852        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
7853        return selectionEnd > selectionStart;
7854    }
7855
7856    /**
7857     * This is a temporary method. Future versions may support multi-locale text.
7858     *
7859     * @return The locale that should be used for a word iterator and a spell checker
7860     * in this TextView, based on the current spell checker settings,
7861     * the current IME's locale, or the system default locale.
7862     * @hide
7863     */
7864    public Locale getTextServicesLocale() {
7865        Locale locale = Locale.getDefault();
7866        final TextServicesManager textServicesManager = (TextServicesManager)
7867                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
7868        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
7869        if (subtype != null) {
7870            locale = new Locale(subtype.getLocale());
7871        }
7872        return locale;
7873    }
7874
7875    void onLocaleChanged() {
7876        // Will be re-created on demand in getWordIterator with the proper new locale
7877        getEditor().mWordIterator = null;
7878    }
7879
7880    /**
7881     * @hide
7882     */
7883    public WordIterator getWordIterator() {
7884        if (getEditor().mWordIterator == null) {
7885            getEditor().mWordIterator = new WordIterator(getTextServicesLocale());
7886        }
7887        return getEditor().mWordIterator;
7888    }
7889
7890    private long getCharRange(int offset) {
7891        final int textLength = mText.length();
7892        if (offset + 1 < textLength) {
7893            final char currentChar = mText.charAt(offset);
7894            final char nextChar = mText.charAt(offset + 1);
7895            if (Character.isSurrogatePair(currentChar, nextChar)) {
7896                return packRangeInLong(offset,  offset + 2);
7897            }
7898        }
7899        if (offset < textLength) {
7900            return packRangeInLong(offset,  offset + 1);
7901        }
7902        if (offset - 2 >= 0) {
7903            final char previousChar = mText.charAt(offset - 1);
7904            final char previousPreviousChar = mText.charAt(offset - 2);
7905            if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
7906                return packRangeInLong(offset - 2,  offset);
7907            }
7908        }
7909        if (offset - 1 >= 0) {
7910            return packRangeInLong(offset - 1,  offset);
7911        }
7912        return packRangeInLong(offset,  offset);
7913    }
7914
7915    private long getLastTouchOffsets() {
7916        SelectionModifierCursorController selectionController = getSelectionController();
7917        final int minOffset = selectionController.getMinTouchOffset();
7918        final int maxOffset = selectionController.getMaxTouchOffset();
7919        return packRangeInLong(minOffset, maxOffset);
7920    }
7921
7922    @Override
7923    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
7924        super.onPopulateAccessibilityEvent(event);
7925
7926        final boolean isPassword = hasPasswordTransformationMethod();
7927        if (!isPassword) {
7928            CharSequence text = getTextForAccessibility();
7929            if (!TextUtils.isEmpty(text)) {
7930                event.getText().add(text);
7931            }
7932        }
7933    }
7934
7935    @Override
7936    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
7937        super.onInitializeAccessibilityEvent(event);
7938
7939        event.setClassName(TextView.class.getName());
7940        final boolean isPassword = hasPasswordTransformationMethod();
7941        event.setPassword(isPassword);
7942
7943        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
7944            event.setFromIndex(Selection.getSelectionStart(mText));
7945            event.setToIndex(Selection.getSelectionEnd(mText));
7946            event.setItemCount(mText.length());
7947        }
7948    }
7949
7950    @Override
7951    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
7952        super.onInitializeAccessibilityNodeInfo(info);
7953
7954        info.setClassName(TextView.class.getName());
7955        final boolean isPassword = hasPasswordTransformationMethod();
7956        info.setPassword(isPassword);
7957
7958        if (!isPassword) {
7959            info.setText(getTextForAccessibility());
7960        }
7961    }
7962
7963    @Override
7964    public void sendAccessibilityEvent(int eventType) {
7965        // Do not send scroll events since first they are not interesting for
7966        // accessibility and second such events a generated too frequently.
7967        // For details see the implementation of bringTextIntoView().
7968        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
7969            return;
7970        }
7971        super.sendAccessibilityEvent(eventType);
7972    }
7973
7974    /**
7975     * Gets the text reported for accessibility purposes. It is the
7976     * text if not empty or the hint.
7977     *
7978     * @return The accessibility text.
7979     */
7980    private CharSequence getTextForAccessibility() {
7981        CharSequence text = getText();
7982        if (TextUtils.isEmpty(text)) {
7983            text = getHint();
7984        }
7985        return text;
7986    }
7987
7988    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7989            int fromIndex, int removedCount, int addedCount) {
7990        AccessibilityEvent event =
7991            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7992        event.setFromIndex(fromIndex);
7993        event.setRemovedCount(removedCount);
7994        event.setAddedCount(addedCount);
7995        event.setBeforeText(beforeText);
7996        sendAccessibilityEventUnchecked(event);
7997    }
7998
7999    /**
8000     * Returns whether this text view is a current input method target.  The
8001     * default implementation just checks with {@link InputMethodManager}.
8002     */
8003    public boolean isInputMethodTarget() {
8004        InputMethodManager imm = InputMethodManager.peekInstance();
8005        return imm != null && imm.isActive(this);
8006    }
8007
8008    // Selection context mode
8009    private static final int ID_SELECT_ALL = android.R.id.selectAll;
8010    private static final int ID_CUT = android.R.id.cut;
8011    private static final int ID_COPY = android.R.id.copy;
8012    private static final int ID_PASTE = android.R.id.paste;
8013
8014    /**
8015     * Called when a context menu option for the text view is selected.  Currently
8016     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8017     * {@link android.R.id#copy} or {@link android.R.id#paste}.
8018     *
8019     * @return true if the context menu item action was performed.
8020     */
8021    public boolean onTextContextMenuItem(int id) {
8022        int min = 0;
8023        int max = mText.length();
8024
8025        if (isFocused()) {
8026            final int selStart = getSelectionStart();
8027            final int selEnd = getSelectionEnd();
8028
8029            min = Math.max(0, Math.min(selStart, selEnd));
8030            max = Math.max(0, Math.max(selStart, selEnd));
8031        }
8032
8033        switch (id) {
8034            case ID_SELECT_ALL:
8035                // This does not enter text selection mode. Text is highlighted, so that it can be
8036                // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
8037                selectAll();
8038                return true;
8039
8040            case ID_PASTE:
8041                paste(min, max);
8042                return true;
8043
8044            case ID_CUT:
8045                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8046                deleteText_internal(min, max);
8047                stopSelectionActionMode();
8048                return true;
8049
8050            case ID_COPY:
8051                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8052                stopSelectionActionMode();
8053                return true;
8054        }
8055        return false;
8056    }
8057
8058    private CharSequence getTransformedText(int start, int end) {
8059        return removeSuggestionSpans(mTransformed.subSequence(start, end));
8060    }
8061
8062    /**
8063     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8064     * by [min, max] when replacing this region by paste.
8065     * Note that if there were two spaces (or more) at that position before, they are kept. We just
8066     * make sure we do not add an extra one from the paste content.
8067     */
8068    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8069        if (paste.length() > 0) {
8070            if (min > 0) {
8071                final char charBefore = mTransformed.charAt(min - 1);
8072                final char charAfter = paste.charAt(0);
8073
8074                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8075                    // Two spaces at beginning of paste: remove one
8076                    final int originalLength = mText.length();
8077                    deleteText_internal(min - 1, min);
8078                    // Due to filters, there is no guarantee that exactly one character was
8079                    // removed: count instead.
8080                    final int delta = mText.length() - originalLength;
8081                    min += delta;
8082                    max += delta;
8083                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8084                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8085                    // No space at beginning of paste: add one
8086                    final int originalLength = mText.length();
8087                    replaceText_internal(min, min, " ");
8088                    // Taking possible filters into account as above.
8089                    final int delta = mText.length() - originalLength;
8090                    min += delta;
8091                    max += delta;
8092                }
8093            }
8094
8095            if (max < mText.length()) {
8096                final char charBefore = paste.charAt(paste.length() - 1);
8097                final char charAfter = mTransformed.charAt(max);
8098
8099                if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8100                    // Two spaces at end of paste: remove one
8101                    deleteText_internal(max, max + 1);
8102                } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8103                        !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8104                    // No space at end of paste: add one
8105                    replaceText_internal(max, max, " ");
8106                }
8107            }
8108        }
8109
8110        return packRangeInLong(min, max);
8111    }
8112
8113    private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
8114        TextView shadowView = (TextView) inflate(mContext,
8115                com.android.internal.R.layout.text_drag_thumbnail, null);
8116
8117        if (shadowView == null) {
8118            throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
8119        }
8120
8121        if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
8122            text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
8123        }
8124        shadowView.setText(text);
8125        shadowView.setTextColor(getTextColors());
8126
8127        shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge);
8128        shadowView.setGravity(Gravity.CENTER);
8129
8130        shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
8131                ViewGroup.LayoutParams.WRAP_CONTENT));
8132
8133        final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8134        shadowView.measure(size, size);
8135
8136        shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight());
8137        shadowView.invalidate();
8138        return new DragShadowBuilder(shadowView);
8139    }
8140
8141    @Override
8142    public boolean performLongClick() {
8143        boolean handled = false;
8144
8145        if (super.performLongClick()) {
8146            handled = true;
8147        }
8148
8149        if (mEditor == null) {
8150            return handled;
8151        }
8152
8153        // Long press in empty space moves cursor and shows the Paste affordance if available.
8154        if (!handled && !isPositionOnText(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY) &&
8155                getEditor().mInsertionControllerEnabled) {
8156            final int offset = getOffsetForPosition(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY);
8157            stopSelectionActionMode();
8158            Selection.setSelection((Spannable) mText, offset);
8159            getInsertionController().showWithActionPopup();
8160            handled = true;
8161        }
8162
8163        if (!handled && getEditor().mSelectionActionMode != null) {
8164            if (touchPositionIsInSelection()) {
8165                // Start a drag
8166                final int start = getSelectionStart();
8167                final int end = getSelectionEnd();
8168                CharSequence selectedText = getTransformedText(start, end);
8169                ClipData data = ClipData.newPlainText(null, selectedText);
8170                DragLocalState localState = new DragLocalState(this, start, end);
8171                startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
8172                stopSelectionActionMode();
8173            } else {
8174                getSelectionController().hide();
8175                selectCurrentWord();
8176                getSelectionController().show();
8177            }
8178            handled = true;
8179        }
8180
8181        // Start a new selection
8182        if (!handled) {
8183            handled = startSelectionActionMode();
8184        }
8185
8186        if (handled) {
8187            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
8188            getEditor().mDiscardNextActionUp = true;
8189        }
8190
8191        return handled;
8192    }
8193
8194    private boolean touchPositionIsInSelection() {
8195        int selectionStart = getSelectionStart();
8196        int selectionEnd = getSelectionEnd();
8197
8198        if (selectionStart == selectionEnd) {
8199            return false;
8200        }
8201
8202        if (selectionStart > selectionEnd) {
8203            int tmp = selectionStart;
8204            selectionStart = selectionEnd;
8205            selectionEnd = tmp;
8206            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
8207        }
8208
8209        SelectionModifierCursorController selectionController = getSelectionController();
8210        int minOffset = selectionController.getMinTouchOffset();
8211        int maxOffset = selectionController.getMaxTouchOffset();
8212
8213        return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
8214    }
8215
8216    private PositionListener getPositionListener() {
8217        if (getEditor().mPositionListener == null) {
8218            getEditor().mPositionListener = new PositionListener();
8219        }
8220        return getEditor().mPositionListener;
8221    }
8222
8223    private interface TextViewPositionListener {
8224        public void updatePosition(int parentPositionX, int parentPositionY,
8225                boolean parentPositionChanged, boolean parentScrolled);
8226    }
8227
8228    private boolean isPositionVisible(int positionX, int positionY) {
8229        synchronized (TEMP_POSITION) {
8230            final float[] position = TEMP_POSITION;
8231            position[0] = positionX;
8232            position[1] = positionY;
8233            View view = this;
8234
8235            while (view != null) {
8236                if (view != this) {
8237                    // Local scroll is already taken into account in positionX/Y
8238                    position[0] -= view.getScrollX();
8239                    position[1] -= view.getScrollY();
8240                }
8241
8242                if (position[0] < 0 || position[1] < 0 ||
8243                        position[0] > view.getWidth() || position[1] > view.getHeight()) {
8244                    return false;
8245                }
8246
8247                if (!view.getMatrix().isIdentity()) {
8248                    view.getMatrix().mapPoints(position);
8249                }
8250
8251                position[0] += view.getLeft();
8252                position[1] += view.getTop();
8253
8254                final ViewParent parent = view.getParent();
8255                if (parent instanceof View) {
8256                    view = (View) parent;
8257                } else {
8258                    // We've reached the ViewRoot, stop iterating
8259                    view = null;
8260                }
8261            }
8262        }
8263
8264        // We've been able to walk up the view hierarchy and the position was never clipped
8265        return true;
8266    }
8267
8268    private boolean isOffsetVisible(int offset) {
8269        final int line = mLayout.getLineForOffset(offset);
8270        final int lineBottom = mLayout.getLineBottom(line);
8271        final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
8272        return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
8273                lineBottom + viewportToContentVerticalOffset());
8274    }
8275
8276    @Override
8277    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8278        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
8279        if (mEditor != null && getEditor().mPositionListener != null) {
8280            getEditor().mPositionListener.onScrollChanged();
8281        }
8282    }
8283
8284    /**
8285     * Removes the suggestion spans.
8286     */
8287    CharSequence removeSuggestionSpans(CharSequence text) {
8288       if (text instanceof Spanned) {
8289           Spannable spannable;
8290           if (text instanceof Spannable) {
8291               spannable = (Spannable) text;
8292           } else {
8293               spannable = new SpannableString(text);
8294               text = spannable;
8295           }
8296
8297           SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
8298           for (int i = 0; i < spans.length; i++) {
8299               spannable.removeSpan(spans[i]);
8300           }
8301       }
8302       return text;
8303    }
8304
8305    void showSuggestions() {
8306        if (getEditor().mSuggestionsPopupWindow == null) {
8307            getEditor().mSuggestionsPopupWindow = new SuggestionsPopupWindow();
8308        }
8309        hideControllers();
8310        getEditor().mSuggestionsPopupWindow.show();
8311    }
8312
8313    boolean areSuggestionsShown() {
8314        return getEditor().mSuggestionsPopupWindow != null && getEditor().mSuggestionsPopupWindow.isShowing();
8315    }
8316
8317    /**
8318     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8319     * by the IME or by the spell checker as the user types. This is done by adding
8320     * {@link SuggestionSpan}s to the text.
8321     *
8322     * When suggestions are enabled (default), this list of suggestions will be displayed when the
8323     * user asks for them on these parts of the text. This value depends on the inputType of this
8324     * TextView.
8325     *
8326     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8327     *
8328     * In addition, the type variation must be one of
8329     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8330     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8331     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8332     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8333     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8334     *
8335     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8336     *
8337     * @return true if the suggestions popup window is enabled, based on the inputType.
8338     */
8339    public boolean isSuggestionsEnabled() {
8340        if (mEditor == null) return false;
8341        if ((getEditor().mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
8342        if ((getEditor().mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
8343
8344        final int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
8345        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8346                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8347                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8348                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8349                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8350    }
8351
8352    /**
8353     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8354     * selection is initiated in this View.
8355     *
8356     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8357     * Paste actions, depending on what this View supports.
8358     *
8359     * A custom implementation can add new entries in the default menu in its
8360     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8361     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8362     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8363     * or {@link android.R.id#paste} ids as parameters.
8364     *
8365     * Returning false from
8366     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8367     * the action mode from being started.
8368     *
8369     * Action click events should be handled by the custom implementation of
8370     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8371     *
8372     * Note that text selection mode is not started when a TextView receives focus and the
8373     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8374     * that case, to allow for quick replacement.
8375     */
8376    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8377        createEditorIfNeeded("custom selection action mode set");
8378        getEditor().mCustomSelectionActionModeCallback = actionModeCallback;
8379    }
8380
8381    /**
8382     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8383     *
8384     * @return The current custom selection callback.
8385     */
8386    public ActionMode.Callback getCustomSelectionActionModeCallback() {
8387        return mEditor == null ? null : getEditor().mCustomSelectionActionModeCallback;
8388    }
8389
8390    /**
8391     *
8392     * @return true if the selection mode was actually started.
8393     */
8394    private boolean startSelectionActionMode() {
8395        if (getEditor().mSelectionActionMode != null) {
8396            // Selection action mode is already started
8397            return false;
8398        }
8399
8400        if (!canSelectText() || !requestFocus()) {
8401            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
8402            return false;
8403        }
8404
8405        if (!hasSelection()) {
8406            // There may already be a selection on device rotation
8407            if (!selectCurrentWord()) {
8408                // No word found under cursor or text selection not permitted.
8409                return false;
8410            }
8411        }
8412
8413        boolean willExtract = extractedTextModeWillBeStarted();
8414
8415        // Do not start the action mode when extracted text will show up full screen, which would
8416        // immediately hide the newly created action bar and would be visually distracting.
8417        if (!willExtract) {
8418            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
8419            getEditor().mSelectionActionMode = startActionMode(actionModeCallback);
8420        }
8421
8422        final boolean selectionStarted = getEditor().mSelectionActionMode != null || willExtract;
8423        if (selectionStarted && !isTextSelectable()) {
8424            // Show the IME to be able to replace text, except when selecting non editable text.
8425            final InputMethodManager imm = InputMethodManager.peekInstance();
8426            if (imm != null) {
8427                imm.showSoftInput(this, 0, null);
8428            }
8429        }
8430
8431        return selectionStarted;
8432    }
8433
8434    private boolean extractedTextModeWillBeStarted() {
8435        if (!(this instanceof ExtractEditText)) {
8436            final InputMethodManager imm = InputMethodManager.peekInstance();
8437            return  imm != null && imm.isFullscreenMode();
8438        }
8439        return false;
8440    }
8441
8442    /**
8443     * @hide
8444     */
8445    protected void stopSelectionActionMode() {
8446        if (getEditor().mSelectionActionMode != null) {
8447            // This will hide the mSelectionModifierCursorController
8448            getEditor().mSelectionActionMode.finish();
8449        }
8450    }
8451
8452    /**
8453     * Paste clipboard content between min and max positions.
8454     */
8455    private void paste(int min, int max) {
8456        ClipboardManager clipboard =
8457            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8458        ClipData clip = clipboard.getPrimaryClip();
8459        if (clip != null) {
8460            boolean didFirst = false;
8461            for (int i=0; i<clip.getItemCount(); i++) {
8462                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
8463                if (paste != null) {
8464                    if (!didFirst) {
8465                        long minMax = prepareSpacesAroundPaste(min, max, paste);
8466                        min = extractRangeStartFromLong(minMax);
8467                        max = extractRangeEndFromLong(minMax);
8468                        Selection.setSelection((Spannable) mText, max);
8469                        ((Editable) mText).replace(min, max, paste);
8470                        didFirst = true;
8471                    } else {
8472                        ((Editable) mText).insert(getSelectionEnd(), "\n");
8473                        ((Editable) mText).insert(getSelectionEnd(), paste);
8474                    }
8475                }
8476            }
8477            stopSelectionActionMode();
8478            LAST_CUT_OR_COPY_TIME = 0;
8479        }
8480    }
8481
8482    private void setPrimaryClip(ClipData clip) {
8483        ClipboardManager clipboard = (ClipboardManager) getContext().
8484                getSystemService(Context.CLIPBOARD_SERVICE);
8485        clipboard.setPrimaryClip(clip);
8486        LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8487    }
8488
8489    private void hideInsertionPointCursorController() {
8490        // No need to create the controller to hide it.
8491        if (getEditor().mInsertionPointCursorController != null) {
8492            getEditor().mInsertionPointCursorController.hide();
8493        }
8494    }
8495
8496    /**
8497     * Hides the insertion controller and stops text selection mode, hiding the selection controller
8498     */
8499    private void hideControllers() {
8500        hideCursorControllers();
8501        hideSpanControllers();
8502    }
8503
8504    private void hideSpanControllers() {
8505        if (mChangeWatcher != null) {
8506            mChangeWatcher.hideControllers();
8507        }
8508    }
8509
8510    private void hideCursorControllers() {
8511        if (getEditor().mSuggestionsPopupWindow != null && !getEditor().mSuggestionsPopupWindow.isShowingUp()) {
8512            // Should be done before hide insertion point controller since it triggers a show of it
8513            getEditor().mSuggestionsPopupWindow.hide();
8514        }
8515        hideInsertionPointCursorController();
8516        stopSelectionActionMode();
8517    }
8518
8519    /**
8520     * Get the character offset closest to the specified absolute position. A typical use case is to
8521     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8522     *
8523     * @param x The horizontal absolute position of a point on screen
8524     * @param y The vertical absolute position of a point on screen
8525     * @return the character offset for the character whose position is closest to the specified
8526     *  position. Returns -1 if there is no layout.
8527     */
8528    public int getOffsetForPosition(float x, float y) {
8529        if (getLayout() == null) return -1;
8530        final int line = getLineAtCoordinate(y);
8531        final int offset = getOffsetAtCoordinate(line, x);
8532        return offset;
8533    }
8534
8535    private float convertToLocalHorizontalCoordinate(float x) {
8536        x -= getTotalPaddingLeft();
8537        // Clamp the position to inside of the view.
8538        x = Math.max(0.0f, x);
8539        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8540        x += getScrollX();
8541        return x;
8542    }
8543
8544    private int getLineAtCoordinate(float y) {
8545        y -= getTotalPaddingTop();
8546        // Clamp the position to inside of the view.
8547        y = Math.max(0.0f, y);
8548        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8549        y += getScrollY();
8550        return getLayout().getLineForVertical((int) y);
8551    }
8552
8553    private int getOffsetAtCoordinate(int line, float x) {
8554        x = convertToLocalHorizontalCoordinate(x);
8555        return getLayout().getOffsetForHorizontal(line, x);
8556    }
8557
8558    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
8559     * in the view. Returns false when the position is in the empty space of left/right of text.
8560     */
8561    private boolean isPositionOnText(float x, float y) {
8562        if (getLayout() == null) return false;
8563
8564        final int line = getLineAtCoordinate(y);
8565        x = convertToLocalHorizontalCoordinate(x);
8566
8567        if (x < getLayout().getLineLeft(line)) return false;
8568        if (x > getLayout().getLineRight(line)) return false;
8569        return true;
8570    }
8571
8572    @Override
8573    public boolean onDragEvent(DragEvent event) {
8574        switch (event.getAction()) {
8575            case DragEvent.ACTION_DRAG_STARTED:
8576                return mEditor != null && hasInsertionController();
8577
8578            case DragEvent.ACTION_DRAG_ENTERED:
8579                TextView.this.requestFocus();
8580                return true;
8581
8582            case DragEvent.ACTION_DRAG_LOCATION:
8583                final int offset = getOffsetForPosition(event.getX(), event.getY());
8584                Selection.setSelection((Spannable)mText, offset);
8585                return true;
8586
8587            case DragEvent.ACTION_DROP:
8588                onDrop(event);
8589                return true;
8590
8591            case DragEvent.ACTION_DRAG_ENDED:
8592            case DragEvent.ACTION_DRAG_EXITED:
8593            default:
8594                return true;
8595        }
8596    }
8597
8598    private void onDrop(DragEvent event) {
8599        StringBuilder content = new StringBuilder("");
8600        ClipData clipData = event.getClipData();
8601        final int itemCount = clipData.getItemCount();
8602        for (int i=0; i < itemCount; i++) {
8603            Item item = clipData.getItemAt(i);
8604            content.append(item.coerceToText(TextView.this.mContext));
8605        }
8606
8607        final int offset = getOffsetForPosition(event.getX(), event.getY());
8608
8609        Object localState = event.getLocalState();
8610        DragLocalState dragLocalState = null;
8611        if (localState instanceof DragLocalState) {
8612            dragLocalState = (DragLocalState) localState;
8613        }
8614        boolean dragDropIntoItself = dragLocalState != null &&
8615                dragLocalState.sourceTextView == this;
8616
8617        if (dragDropIntoItself) {
8618            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
8619                // A drop inside the original selection discards the drop.
8620                return;
8621            }
8622        }
8623
8624        final int originalLength = mText.length();
8625        long minMax = prepareSpacesAroundPaste(offset, offset, content);
8626        int min = extractRangeStartFromLong(minMax);
8627        int max = extractRangeEndFromLong(minMax);
8628
8629        Selection.setSelection((Spannable) mText, max);
8630        replaceText_internal(min, max, content);
8631
8632        if (dragDropIntoItself) {
8633            int dragSourceStart = dragLocalState.start;
8634            int dragSourceEnd = dragLocalState.end;
8635            if (max <= dragSourceStart) {
8636                // Inserting text before selection has shifted positions
8637                final int shift = mText.length() - originalLength;
8638                dragSourceStart += shift;
8639                dragSourceEnd += shift;
8640            }
8641
8642            // Delete original selection
8643            deleteText_internal(dragSourceStart, dragSourceEnd);
8644
8645            // Make sure we do not leave two adjacent spaces.
8646            if ((dragSourceStart == 0 ||
8647                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
8648                    (dragSourceStart == mText.length() ||
8649                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
8650                final int pos = dragSourceStart == mText.length() ?
8651                        dragSourceStart - 1 : dragSourceStart;
8652                deleteText_internal(pos, pos + 1);
8653            }
8654        }
8655    }
8656
8657    /**
8658     * @return True if this view supports insertion handles.
8659     */
8660    boolean hasInsertionController() {
8661        return getEditor().mInsertionControllerEnabled;
8662    }
8663
8664    /**
8665     * @return True if this view supports selection handles.
8666     */
8667    boolean hasSelectionController() {
8668        return getEditor().mSelectionControllerEnabled;
8669    }
8670
8671    InsertionPointCursorController getInsertionController() {
8672        if (!getEditor().mInsertionControllerEnabled) {
8673            return null;
8674        }
8675
8676        if (getEditor().mInsertionPointCursorController == null) {
8677            getEditor().mInsertionPointCursorController = new InsertionPointCursorController();
8678
8679            final ViewTreeObserver observer = getViewTreeObserver();
8680            observer.addOnTouchModeChangeListener(getEditor().mInsertionPointCursorController);
8681        }
8682
8683        return getEditor().mInsertionPointCursorController;
8684    }
8685
8686    SelectionModifierCursorController getSelectionController() {
8687        if (!getEditor().mSelectionControllerEnabled) {
8688            return null;
8689        }
8690
8691        if (getEditor().mSelectionModifierCursorController == null) {
8692            getEditor().mSelectionModifierCursorController = new SelectionModifierCursorController();
8693
8694            final ViewTreeObserver observer = getViewTreeObserver();
8695            observer.addOnTouchModeChangeListener(getEditor().mSelectionModifierCursorController);
8696        }
8697
8698        return getEditor().mSelectionModifierCursorController;
8699    }
8700
8701    boolean isInBatchEditMode() {
8702        if (mEditor == null) return false;
8703        final InputMethodState ims = getEditor().mInputMethodState;
8704        if (ims != null) {
8705            return ims.mBatchEditNesting > 0;
8706        }
8707        return getEditor().mInBatchEditControllers;
8708    }
8709
8710    @Override
8711    public void onResolveTextDirection() {
8712        if (hasPasswordTransformationMethod()) {
8713            mTextDir = TextDirectionHeuristics.LOCALE;
8714            return;
8715        }
8716
8717        // Always need to resolve layout direction first
8718        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
8719
8720        // Now, we can select the heuristic
8721        int textDir = getResolvedTextDirection();
8722        switch (textDir) {
8723            default:
8724            case TEXT_DIRECTION_FIRST_STRONG:
8725                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
8726                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
8727                break;
8728            case TEXT_DIRECTION_ANY_RTL:
8729                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
8730                break;
8731            case TEXT_DIRECTION_LTR:
8732                mTextDir = TextDirectionHeuristics.LTR;
8733                break;
8734            case TEXT_DIRECTION_RTL:
8735                mTextDir = TextDirectionHeuristics.RTL;
8736                break;
8737            case TEXT_DIRECTION_LOCALE:
8738                mTextDir = TextDirectionHeuristics.LOCALE;
8739                break;
8740        }
8741    }
8742
8743    /**
8744     * Subclasses will need to override this method to implement their own way of resolving
8745     * drawables depending on the layout direction.
8746     *
8747     * A call to the super method will be required from the subclasses implementation.
8748     */
8749    protected void resolveDrawables() {
8750        // No need to resolve twice
8751        if (mResolvedDrawables) {
8752            return;
8753        }
8754        // No drawable to resolve
8755        if (mDrawables == null) {
8756            return;
8757        }
8758        // No relative drawable to resolve
8759        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
8760            mResolvedDrawables = true;
8761            return;
8762        }
8763
8764        Drawables dr = mDrawables;
8765        switch(getResolvedLayoutDirection()) {
8766            case LAYOUT_DIRECTION_RTL:
8767                if (dr.mDrawableStart != null) {
8768                    dr.mDrawableRight = dr.mDrawableStart;
8769
8770                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
8771                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
8772                }
8773                if (dr.mDrawableEnd != null) {
8774                    dr.mDrawableLeft = dr.mDrawableEnd;
8775
8776                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
8777                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
8778                }
8779                break;
8780
8781            case LAYOUT_DIRECTION_LTR:
8782            default:
8783                if (dr.mDrawableStart != null) {
8784                    dr.mDrawableLeft = dr.mDrawableStart;
8785
8786                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
8787                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
8788                }
8789                if (dr.mDrawableEnd != null) {
8790                    dr.mDrawableRight = dr.mDrawableEnd;
8791
8792                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
8793                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
8794                }
8795                break;
8796        }
8797        mResolvedDrawables = true;
8798    }
8799
8800    protected void resetResolvedDrawables() {
8801        mResolvedDrawables = false;
8802    }
8803
8804    /**
8805     * @hide
8806     */
8807    protected void viewClicked(InputMethodManager imm) {
8808        if (imm != null) {
8809            imm.viewClicked(this);
8810        }
8811    }
8812
8813    /**
8814     * Deletes the range of text [start, end[.
8815     * @hide
8816     */
8817    protected void deleteText_internal(int start, int end) {
8818        ((Editable) mText).delete(start, end);
8819    }
8820
8821    /**
8822     * Replaces the range of text [start, end[ by replacement text
8823     * @hide
8824     */
8825    protected void replaceText_internal(int start, int end, CharSequence text) {
8826        ((Editable) mText).replace(start, end, text);
8827    }
8828
8829    /**
8830     * Sets a span on the specified range of text
8831     * @hide
8832     */
8833    protected void setSpan_internal(Object span, int start, int end, int flags) {
8834        ((Editable) mText).setSpan(span, start, end, flags);
8835    }
8836
8837    /**
8838     * Moves the cursor to the specified offset position in text
8839     * @hide
8840     */
8841    protected void setCursorPosition_internal(int start, int end) {
8842        Selection.setSelection(((Editable) mText), start, end);
8843    }
8844
8845    /**
8846     * An Editor should be created as soon as any of the editable-specific fields (grouped
8847     * inside the Editor object) is assigned to a non-default value.
8848     * This method will create the Editor if needed.
8849     *
8850     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8851     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8852     * Editor for backward compatibility, as soon as one of these fields is assigned.
8853     *
8854     * Also note that for performance reasons, the mEditor is created when needed, but not
8855     * reset when no more edit-specific fields are needed.
8856     */
8857    private void createEditorIfNeeded(String reason) {
8858        if (mEditor == null) {
8859            if (!(this instanceof EditText)) {
8860                Log.e(LOG_TAG + " EDITOR", "Creating Editor on TextView. " + reason);
8861            }
8862            mEditor = new Editor();
8863        } else {
8864            if (!(this instanceof EditText)) {
8865                Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason);
8866            }
8867        }
8868    }
8869
8870    private Editor getEditor() {
8871        if (mEditor == null) {
8872            //createEditorIfNeeded("Problem: mEditor is not initialized!");
8873            Log.e(LOG_TAG, "mEditor not initialized. Please send a bug report to debunne@");
8874        }
8875        return mEditor;
8876    }
8877
8878    /**
8879     * User interface state that is stored by TextView for implementing
8880     * {@link View#onSaveInstanceState}.
8881     */
8882    public static class SavedState extends BaseSavedState {
8883        int selStart;
8884        int selEnd;
8885        CharSequence text;
8886        boolean frozenWithFocus;
8887        CharSequence error;
8888
8889        SavedState(Parcelable superState) {
8890            super(superState);
8891        }
8892
8893        @Override
8894        public void writeToParcel(Parcel out, int flags) {
8895            super.writeToParcel(out, flags);
8896            out.writeInt(selStart);
8897            out.writeInt(selEnd);
8898            out.writeInt(frozenWithFocus ? 1 : 0);
8899            TextUtils.writeToParcel(text, out, flags);
8900
8901            if (error == null) {
8902                out.writeInt(0);
8903            } else {
8904                out.writeInt(1);
8905                TextUtils.writeToParcel(error, out, flags);
8906            }
8907        }
8908
8909        @Override
8910        public String toString() {
8911            String str = "TextView.SavedState{"
8912                    + Integer.toHexString(System.identityHashCode(this))
8913                    + " start=" + selStart + " end=" + selEnd;
8914            if (text != null) {
8915                str += " text=" + text;
8916            }
8917            return str + "}";
8918        }
8919
8920        @SuppressWarnings("hiding")
8921        public static final Parcelable.Creator<SavedState> CREATOR
8922                = new Parcelable.Creator<SavedState>() {
8923            public SavedState createFromParcel(Parcel in) {
8924                return new SavedState(in);
8925            }
8926
8927            public SavedState[] newArray(int size) {
8928                return new SavedState[size];
8929            }
8930        };
8931
8932        private SavedState(Parcel in) {
8933            super(in);
8934            selStart = in.readInt();
8935            selEnd = in.readInt();
8936            frozenWithFocus = (in.readInt() != 0);
8937            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8938
8939            if (in.readInt() != 0) {
8940                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8941            }
8942        }
8943    }
8944
8945    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8946        private char[] mChars;
8947        private int mStart, mLength;
8948
8949        public CharWrapper(char[] chars, int start, int len) {
8950            mChars = chars;
8951            mStart = start;
8952            mLength = len;
8953        }
8954
8955        /* package */ void set(char[] chars, int start, int len) {
8956            mChars = chars;
8957            mStart = start;
8958            mLength = len;
8959        }
8960
8961        public int length() {
8962            return mLength;
8963        }
8964
8965        public char charAt(int off) {
8966            return mChars[off + mStart];
8967        }
8968
8969        @Override
8970        public String toString() {
8971            return new String(mChars, mStart, mLength);
8972        }
8973
8974        public CharSequence subSequence(int start, int end) {
8975            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8976                throw new IndexOutOfBoundsException(start + ", " + end);
8977            }
8978
8979            return new String(mChars, start + mStart, end - start);
8980        }
8981
8982        public void getChars(int start, int end, char[] buf, int off) {
8983            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8984                throw new IndexOutOfBoundsException(start + ", " + end);
8985            }
8986
8987            System.arraycopy(mChars, start + mStart, buf, off, end - start);
8988        }
8989
8990        public void drawText(Canvas c, int start, int end,
8991                             float x, float y, Paint p) {
8992            c.drawText(mChars, start + mStart, end - start, x, y, p);
8993        }
8994
8995        public void drawTextRun(Canvas c, int start, int end,
8996                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
8997            int count = end - start;
8998            int contextCount = contextEnd - contextStart;
8999            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9000                    contextCount, x, y, flags, p);
9001        }
9002
9003        public float measureText(int start, int end, Paint p) {
9004            return p.measureText(mChars, start + mStart, end - start);
9005        }
9006
9007        public int getTextWidths(int start, int end, float[] widths, Paint p) {
9008            return p.getTextWidths(mChars, start + mStart, end - start, widths);
9009        }
9010
9011        public float getTextRunAdvances(int start, int end, int contextStart,
9012                int contextEnd, int flags, float[] advances, int advancesIndex,
9013                Paint p) {
9014            int count = end - start;
9015            int contextCount = contextEnd - contextStart;
9016            return p.getTextRunAdvances(mChars, start + mStart, count,
9017                    contextStart + mStart, contextCount, flags, advances,
9018                    advancesIndex);
9019        }
9020
9021        public float getTextRunAdvances(int start, int end, int contextStart,
9022                int contextEnd, int flags, float[] advances, int advancesIndex,
9023                Paint p, int reserved) {
9024            int count = end - start;
9025            int contextCount = contextEnd - contextStart;
9026            return p.getTextRunAdvances(mChars, start + mStart, count,
9027                    contextStart + mStart, contextCount, flags, advances,
9028                    advancesIndex, reserved);
9029        }
9030
9031        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
9032                int offset, int cursorOpt, Paint p) {
9033            int contextCount = contextEnd - contextStart;
9034            return p.getTextRunCursor(mChars, contextStart + mStart,
9035                    contextCount, flags, offset + mStart, cursorOpt);
9036        }
9037    }
9038
9039    private static class ErrorPopup extends PopupWindow {
9040        private boolean mAbove = false;
9041        private final TextView mView;
9042        private int mPopupInlineErrorBackgroundId = 0;
9043        private int mPopupInlineErrorAboveBackgroundId = 0;
9044
9045        ErrorPopup(TextView v, int width, int height) {
9046            super(v, width, height);
9047            mView = v;
9048            // Make sure the TextView has a background set as it will be used the first time it is
9049            // shown and positionned. Initialized with below background, which should have
9050            // dimensions identical to the above version for this to work (and is more likely).
9051            mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
9052                    com.android.internal.R.styleable.Theme_errorMessageBackground);
9053            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
9054        }
9055
9056        void fixDirection(boolean above) {
9057            mAbove = above;
9058
9059            if (above) {
9060                mPopupInlineErrorAboveBackgroundId =
9061                    getResourceId(mPopupInlineErrorAboveBackgroundId,
9062                            com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
9063            } else {
9064                mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
9065                        com.android.internal.R.styleable.Theme_errorMessageBackground);
9066            }
9067
9068            mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
9069                mPopupInlineErrorBackgroundId);
9070        }
9071
9072        private int getResourceId(int currentId, int index) {
9073            if (currentId == 0) {
9074                TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
9075                        R.styleable.Theme);
9076                currentId = styledAttributes.getResourceId(index, 0);
9077                styledAttributes.recycle();
9078            }
9079            return currentId;
9080        }
9081
9082        @Override
9083        public void update(int x, int y, int w, int h, boolean force) {
9084            super.update(x, y, w, h, force);
9085
9086            boolean above = isAboveAnchor();
9087            if (above != mAbove) {
9088                fixDirection(above);
9089            }
9090        }
9091    }
9092
9093    private class CorrectionHighlighter {
9094        private final Path mPath = new Path();
9095        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
9096        private int mStart, mEnd;
9097        private long mFadingStartTime;
9098        private final static int FADE_OUT_DURATION = 400;
9099
9100        public CorrectionHighlighter() {
9101            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
9102            mPaint.setStyle(Paint.Style.FILL);
9103        }
9104
9105        public void highlight(CorrectionInfo info) {
9106            mStart = info.getOffset();
9107            mEnd = mStart + info.getNewText().length();
9108            mFadingStartTime = SystemClock.uptimeMillis();
9109
9110            if (mStart < 0 || mEnd < 0) {
9111                stopAnimation();
9112            }
9113        }
9114
9115        public void draw(Canvas canvas, int cursorOffsetVertical) {
9116            if (updatePath() && updatePaint()) {
9117                if (cursorOffsetVertical != 0) {
9118                    canvas.translate(0, cursorOffsetVertical);
9119                }
9120
9121                canvas.drawPath(mPath, mPaint);
9122
9123                if (cursorOffsetVertical != 0) {
9124                    canvas.translate(0, -cursorOffsetVertical);
9125                }
9126                invalidate(true); // TODO invalidate cursor region only
9127            } else {
9128                stopAnimation();
9129                invalidate(false); // TODO invalidate cursor region only
9130            }
9131        }
9132
9133        private boolean updatePaint() {
9134            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
9135            if (duration > FADE_OUT_DURATION) return false;
9136
9137            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
9138            final int highlightColorAlpha = Color.alpha(mHighlightColor);
9139            final int color = (mHighlightColor & 0x00FFFFFF) +
9140                    ((int) (highlightColorAlpha * coef) << 24);
9141            mPaint.setColor(color);
9142            return true;
9143        }
9144
9145        private boolean updatePath() {
9146            final Layout layout = TextView.this.mLayout;
9147            if (layout == null) return false;
9148
9149            // Update in case text is edited while the animation is run
9150            final int length = mText.length();
9151            int start = Math.min(length, mStart);
9152            int end = Math.min(length, mEnd);
9153
9154            mPath.reset();
9155            TextView.this.mLayout.getSelectionPath(start, end, mPath);
9156            return true;
9157        }
9158
9159        private void invalidate(boolean delayed) {
9160            if (TextView.this.mLayout == null) return;
9161
9162            synchronized (TEMP_RECTF) {
9163                mPath.computeBounds(TEMP_RECTF, false);
9164
9165                int left = getCompoundPaddingLeft();
9166                int top = getExtendedPaddingTop() + getVerticalOffset(true);
9167
9168                if (delayed) {
9169                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
9170                            left + (int) TEMP_RECTF.left, top + (int) TEMP_RECTF.top,
9171                            left + (int) TEMP_RECTF.right, top + (int) TEMP_RECTF.bottom);
9172                } else {
9173                    TextView.this.postInvalidate((int) TEMP_RECTF.left, (int) TEMP_RECTF.top,
9174                            (int) TEMP_RECTF.right, (int) TEMP_RECTF.bottom);
9175                }
9176            }
9177        }
9178
9179        private void stopAnimation() {
9180            TextView.this.getEditor().mCorrectionHighlighter = null;
9181        }
9182    }
9183
9184    private static final class Marquee extends Handler {
9185        // TODO: Add an option to configure this
9186        private static final float MARQUEE_DELTA_MAX = 0.07f;
9187        private static final int MARQUEE_DELAY = 1200;
9188        private static final int MARQUEE_RESTART_DELAY = 1200;
9189        private static final int MARQUEE_RESOLUTION = 1000 / 30;
9190        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
9191
9192        private static final byte MARQUEE_STOPPED = 0x0;
9193        private static final byte MARQUEE_STARTING = 0x1;
9194        private static final byte MARQUEE_RUNNING = 0x2;
9195
9196        private static final int MESSAGE_START = 0x1;
9197        private static final int MESSAGE_TICK = 0x2;
9198        private static final int MESSAGE_RESTART = 0x3;
9199
9200        private final WeakReference<TextView> mView;
9201
9202        private byte mStatus = MARQUEE_STOPPED;
9203        private final float mScrollUnit;
9204        private float mMaxScroll;
9205        float mMaxFadeScroll;
9206        private float mGhostStart;
9207        private float mGhostOffset;
9208        private float mFadeStop;
9209        private int mRepeatLimit;
9210
9211        float mScroll;
9212
9213        Marquee(TextView v) {
9214            final float density = v.getContext().getResources().getDisplayMetrics().density;
9215            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
9216            mView = new WeakReference<TextView>(v);
9217        }
9218
9219        @Override
9220        public void handleMessage(Message msg) {
9221            switch (msg.what) {
9222                case MESSAGE_START:
9223                    mStatus = MARQUEE_RUNNING;
9224                    tick();
9225                    break;
9226                case MESSAGE_TICK:
9227                    tick();
9228                    break;
9229                case MESSAGE_RESTART:
9230                    if (mStatus == MARQUEE_RUNNING) {
9231                        if (mRepeatLimit >= 0) {
9232                            mRepeatLimit--;
9233                        }
9234                        start(mRepeatLimit);
9235                    }
9236                    break;
9237            }
9238        }
9239
9240        void tick() {
9241            if (mStatus != MARQUEE_RUNNING) {
9242                return;
9243            }
9244
9245            removeMessages(MESSAGE_TICK);
9246
9247            final TextView textView = mView.get();
9248            if (textView != null && (textView.isFocused() || textView.isSelected())) {
9249                mScroll += mScrollUnit;
9250                if (mScroll > mMaxScroll) {
9251                    mScroll = mMaxScroll;
9252                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
9253                } else {
9254                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
9255                }
9256                textView.invalidate();
9257            }
9258        }
9259
9260        void stop() {
9261            mStatus = MARQUEE_STOPPED;
9262            removeMessages(MESSAGE_START);
9263            removeMessages(MESSAGE_RESTART);
9264            removeMessages(MESSAGE_TICK);
9265            resetScroll();
9266        }
9267
9268        private void resetScroll() {
9269            mScroll = 0.0f;
9270            final TextView textView = mView.get();
9271            if (textView != null) textView.invalidate();
9272        }
9273
9274        void start(int repeatLimit) {
9275            if (repeatLimit == 0) {
9276                stop();
9277                return;
9278            }
9279            mRepeatLimit = repeatLimit;
9280            final TextView textView = mView.get();
9281            if (textView != null && textView.mLayout != null) {
9282                mStatus = MARQUEE_STARTING;
9283                mScroll = 0.0f;
9284                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9285                        textView.getCompoundPaddingRight();
9286                final float lineWidth = textView.mLayout.getLineWidth(0);
9287                final float gap = textWidth / 3.0f;
9288                mGhostStart = lineWidth - textWidth + gap;
9289                mMaxScroll = mGhostStart + textWidth;
9290                mGhostOffset = lineWidth + gap;
9291                mFadeStop = lineWidth + textWidth / 6.0f;
9292                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9293
9294                textView.invalidate();
9295                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9296            }
9297        }
9298
9299        float getGhostOffset() {
9300            return mGhostOffset;
9301        }
9302
9303        boolean shouldDrawLeftFade() {
9304            return mScroll <= mFadeStop;
9305        }
9306
9307        boolean shouldDrawGhost() {
9308            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9309        }
9310
9311        boolean isRunning() {
9312            return mStatus == MARQUEE_RUNNING;
9313        }
9314
9315        boolean isStopped() {
9316            return mStatus == MARQUEE_STOPPED;
9317        }
9318    }
9319
9320    /**
9321     * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
9322     * pop-up should be displayed.
9323     */
9324    private class EasyEditSpanController {
9325
9326        private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
9327
9328        private EasyEditPopupWindow mPopupWindow;
9329
9330        private EasyEditSpan mEasyEditSpan;
9331
9332        private Runnable mHidePopup;
9333
9334        private void hide() {
9335            if (mPopupWindow != null) {
9336                mPopupWindow.hide();
9337                TextView.this.removeCallbacks(mHidePopup);
9338            }
9339            removeSpans(mText);
9340            mEasyEditSpan = null;
9341        }
9342
9343        /**
9344         * Monitors the changes in the text.
9345         *
9346         * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
9347         * as the notifications are not sent when a spannable (with spans) is inserted.
9348         */
9349        public void onTextChange(CharSequence buffer) {
9350            adjustSpans(mText);
9351
9352            if (getWindowVisibility() != View.VISIBLE) {
9353                // The window is not visible yet, ignore the text change.
9354                return;
9355            }
9356
9357            if (mLayout == null) {
9358                // The view has not been layout yet, ignore the text change
9359                return;
9360            }
9361
9362            InputMethodManager imm = InputMethodManager.peekInstance();
9363            if (!(TextView.this instanceof ExtractEditText)
9364                    && imm != null && imm.isFullscreenMode()) {
9365                // The input is in extract mode. We do not have to handle the easy edit in the
9366                // original TextView, as the ExtractEditText will do
9367                return;
9368            }
9369
9370            // Remove the current easy edit span, as the text changed, and remove the pop-up
9371            // (if any)
9372            if (mEasyEditSpan != null) {
9373                if (mText instanceof Spannable) {
9374                    ((Spannable) mText).removeSpan(mEasyEditSpan);
9375                }
9376                mEasyEditSpan = null;
9377            }
9378            if (mPopupWindow != null && mPopupWindow.isShowing()) {
9379                mPopupWindow.hide();
9380            }
9381
9382            // Display the new easy edit span (if any).
9383            if (buffer instanceof Spanned) {
9384                mEasyEditSpan = getSpan((Spanned) buffer);
9385                if (mEasyEditSpan != null) {
9386                    if (mPopupWindow == null) {
9387                        mPopupWindow = new EasyEditPopupWindow();
9388                        mHidePopup = new Runnable() {
9389                            @Override
9390                            public void run() {
9391                                hide();
9392                            }
9393                        };
9394                    }
9395                    mPopupWindow.show(mEasyEditSpan);
9396                    TextView.this.removeCallbacks(mHidePopup);
9397                    TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
9398                }
9399            }
9400        }
9401
9402        /**
9403         * Adjusts the spans by removing all of them except the last one.
9404         */
9405        private void adjustSpans(CharSequence buffer) {
9406            // This method enforces that only one easy edit span is attached to the text.
9407            // A better way to enforce this would be to listen for onSpanAdded, but this method
9408            // cannot be used in this scenario as no notification is triggered when a text with
9409            // spans is inserted into a text.
9410            if (buffer instanceof Spannable) {
9411                Spannable spannable = (Spannable) buffer;
9412                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
9413                        EasyEditSpan.class);
9414                for (int i = 0; i < spans.length - 1; i++) {
9415                    spannable.removeSpan(spans[i]);
9416                }
9417            }
9418        }
9419
9420        /**
9421         * Removes all the {@link EasyEditSpan} currently attached.
9422         */
9423        private void removeSpans(CharSequence buffer) {
9424            if (buffer instanceof Spannable) {
9425                Spannable spannable = (Spannable) buffer;
9426                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
9427                        EasyEditSpan.class);
9428                for (int i = 0; i < spans.length; i++) {
9429                    spannable.removeSpan(spans[i]);
9430                }
9431            }
9432        }
9433
9434        private EasyEditSpan getSpan(Spanned spanned) {
9435            EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
9436                    EasyEditSpan.class);
9437            if (easyEditSpans.length == 0) {
9438                return null;
9439            } else {
9440                return easyEditSpans[0];
9441            }
9442        }
9443    }
9444
9445    /**
9446     * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
9447     * by {@link EasyEditSpanController}.
9448     */
9449    private class EasyEditPopupWindow extends PinnedPopupWindow
9450            implements OnClickListener {
9451        private static final int POPUP_TEXT_LAYOUT =
9452                com.android.internal.R.layout.text_edit_action_popup_text;
9453        private TextView mDeleteTextView;
9454        private EasyEditSpan mEasyEditSpan;
9455
9456        @Override
9457        protected void createPopupWindow() {
9458            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
9459                    com.android.internal.R.attr.textSelectHandleWindowStyle);
9460            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
9461            mPopupWindow.setClippingEnabled(true);
9462        }
9463
9464        @Override
9465        protected void initContentView() {
9466            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
9467            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
9468            mContentView = linearLayout;
9469            mContentView.setBackgroundResource(
9470                    com.android.internal.R.drawable.text_edit_side_paste_window);
9471
9472            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
9473                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9474
9475            LayoutParams wrapContent = new LayoutParams(
9476                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
9477
9478            mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
9479            mDeleteTextView.setLayoutParams(wrapContent);
9480            mDeleteTextView.setText(com.android.internal.R.string.delete);
9481            mDeleteTextView.setOnClickListener(this);
9482            mContentView.addView(mDeleteTextView);
9483        }
9484
9485        public void show(EasyEditSpan easyEditSpan) {
9486            mEasyEditSpan = easyEditSpan;
9487            super.show();
9488        }
9489
9490        @Override
9491        public void onClick(View view) {
9492            if (view == mDeleteTextView) {
9493                Editable editable = (Editable) mText;
9494                int start = editable.getSpanStart(mEasyEditSpan);
9495                int end = editable.getSpanEnd(mEasyEditSpan);
9496                if (start >= 0 && end >= 0) {
9497                    deleteText_internal(start, end);
9498                }
9499            }
9500        }
9501
9502        @Override
9503        protected int getTextOffset() {
9504            // Place the pop-up at the end of the span
9505            Editable editable = (Editable) mText;
9506            return editable.getSpanEnd(mEasyEditSpan);
9507        }
9508
9509        @Override
9510        protected int getVerticalLocalPosition(int line) {
9511            return mLayout.getLineBottom(line);
9512        }
9513
9514        @Override
9515        protected int clipVertically(int positionY) {
9516            // As we display the pop-up below the span, no vertical clipping is required.
9517            return positionY;
9518        }
9519    }
9520
9521    private class ChangeWatcher implements TextWatcher, SpanWatcher {
9522
9523        private CharSequence mBeforeText;
9524
9525        private EasyEditSpanController mEasyEditSpanController;
9526
9527        private ChangeWatcher() {
9528            mEasyEditSpanController = new EasyEditSpanController();
9529        }
9530
9531        public void beforeTextChanged(CharSequence buffer, int start,
9532                                      int before, int after) {
9533            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9534                    + " before=" + before + " after=" + after + ": " + buffer);
9535
9536            if (AccessibilityManager.getInstance(mContext).isEnabled()
9537                    && !isPasswordInputType(getInputType())
9538                    && !hasPasswordTransformationMethod()) {
9539                mBeforeText = buffer.toString();
9540            }
9541
9542            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9543        }
9544
9545        public void onTextChanged(CharSequence buffer, int start,
9546                                  int before, int after) {
9547            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9548                    + " before=" + before + " after=" + after + ": " + buffer);
9549            TextView.this.handleTextChanged(buffer, start, before, after);
9550
9551            mEasyEditSpanController.onTextChange(buffer);
9552
9553            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9554                    (isFocused() || isSelected() && isShown())) {
9555                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9556                mBeforeText = null;
9557            }
9558        }
9559
9560        public void afterTextChanged(Editable buffer) {
9561            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9562            TextView.this.sendAfterTextChanged(buffer);
9563
9564            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9565                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9566            }
9567        }
9568
9569        public void onSpanChanged(Spannable buf,
9570                                  Object what, int s, int e, int st, int en) {
9571            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9572                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9573            TextView.this.spanChange(buf, what, s, st, e, en);
9574        }
9575
9576        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9577            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9578                    + " what=" + what + ": " + buf);
9579            TextView.this.spanChange(buf, what, -1, s, -1, e);
9580        }
9581
9582        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9583            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9584                    + " what=" + what + ": " + buf);
9585            TextView.this.spanChange(buf, what, s, -1, e, -1);
9586        }
9587
9588        private void hideControllers() {
9589            mEasyEditSpanController.hide();
9590        }
9591    }
9592
9593    private static class Blink extends Handler implements Runnable {
9594        private final WeakReference<TextView> mView;
9595        private boolean mCancelled;
9596
9597        public Blink(TextView v) {
9598            mView = new WeakReference<TextView>(v);
9599        }
9600
9601        public void run() {
9602            if (mCancelled) {
9603                return;
9604            }
9605
9606            removeCallbacks(Blink.this);
9607
9608            TextView tv = mView.get();
9609
9610            if (tv != null && tv.shouldBlink()) {
9611                if (tv.mLayout != null) {
9612                    tv.invalidateCursorPath();
9613                }
9614
9615                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
9616            }
9617        }
9618
9619        void cancel() {
9620            if (!mCancelled) {
9621                removeCallbacks(Blink.this);
9622                mCancelled = true;
9623            }
9624        }
9625
9626        void uncancel() {
9627            mCancelled = false;
9628        }
9629    }
9630
9631    private static class DragLocalState {
9632        public TextView sourceTextView;
9633        public int start, end;
9634
9635        public DragLocalState(TextView sourceTextView, int start, int end) {
9636            this.sourceTextView = sourceTextView;
9637            this.start = start;
9638            this.end = end;
9639        }
9640    }
9641
9642    private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
9643        // 3 handles
9644        // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
9645        private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
9646        private TextViewPositionListener[] mPositionListeners =
9647                new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
9648        private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
9649        private boolean mPositionHasChanged = true;
9650        // Absolute position of the TextView with respect to its parent window
9651        private int mPositionX, mPositionY;
9652        private int mNumberOfListeners;
9653        private boolean mScrollHasChanged;
9654        final int[] mTempCoords = new int[2];
9655
9656        public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
9657            if (mNumberOfListeners == 0) {
9658                updatePosition();
9659                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9660                vto.addOnPreDrawListener(this);
9661            }
9662
9663            int emptySlotIndex = -1;
9664            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9665                TextViewPositionListener listener = mPositionListeners[i];
9666                if (listener == positionListener) {
9667                    return;
9668                } else if (emptySlotIndex < 0 && listener == null) {
9669                    emptySlotIndex = i;
9670                }
9671            }
9672
9673            mPositionListeners[emptySlotIndex] = positionListener;
9674            mCanMove[emptySlotIndex] = canMove;
9675            mNumberOfListeners++;
9676        }
9677
9678        public void removeSubscriber(TextViewPositionListener positionListener) {
9679            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9680                if (mPositionListeners[i] == positionListener) {
9681                    mPositionListeners[i] = null;
9682                    mNumberOfListeners--;
9683                    break;
9684                }
9685            }
9686
9687            if (mNumberOfListeners == 0) {
9688                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9689                vto.removeOnPreDrawListener(this);
9690            }
9691        }
9692
9693        public int getPositionX() {
9694            return mPositionX;
9695        }
9696
9697        public int getPositionY() {
9698            return mPositionY;
9699        }
9700
9701        @Override
9702        public boolean onPreDraw() {
9703            updatePosition();
9704
9705            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9706                if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
9707                    TextViewPositionListener positionListener = mPositionListeners[i];
9708                    if (positionListener != null) {
9709                        positionListener.updatePosition(mPositionX, mPositionY,
9710                                mPositionHasChanged, mScrollHasChanged);
9711                    }
9712                }
9713            }
9714
9715            mScrollHasChanged = false;
9716            return true;
9717        }
9718
9719        private void updatePosition() {
9720            TextView.this.getLocationInWindow(mTempCoords);
9721
9722            mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
9723
9724            mPositionX = mTempCoords[0];
9725            mPositionY = mTempCoords[1];
9726        }
9727
9728        public void onScrollChanged() {
9729            mScrollHasChanged = true;
9730        }
9731    }
9732
9733    private abstract class PinnedPopupWindow implements TextViewPositionListener {
9734        protected PopupWindow mPopupWindow;
9735        protected ViewGroup mContentView;
9736        int mPositionX, mPositionY;
9737
9738        protected abstract void createPopupWindow();
9739        protected abstract void initContentView();
9740        protected abstract int getTextOffset();
9741        protected abstract int getVerticalLocalPosition(int line);
9742        protected abstract int clipVertically(int positionY);
9743
9744        public PinnedPopupWindow() {
9745            createPopupWindow();
9746
9747            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
9748            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
9749            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
9750
9751            initContentView();
9752
9753            LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
9754                    ViewGroup.LayoutParams.WRAP_CONTENT);
9755            mContentView.setLayoutParams(wrapContent);
9756
9757            mPopupWindow.setContentView(mContentView);
9758        }
9759
9760        public void show() {
9761            TextView.this.getPositionListener().addSubscriber(this, false /* offset is fixed */);
9762
9763            computeLocalPosition();
9764
9765            final PositionListener positionListener = TextView.this.getPositionListener();
9766            updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
9767        }
9768
9769        protected void measureContent() {
9770            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9771            mContentView.measure(
9772                    View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
9773                            View.MeasureSpec.AT_MOST),
9774                    View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
9775                            View.MeasureSpec.AT_MOST));
9776        }
9777
9778        /* The popup window will be horizontally centered on the getTextOffset() and vertically
9779         * positioned according to viewportToContentHorizontalOffset.
9780         *
9781         * This method assumes that mContentView has properly been measured from its content. */
9782        private void computeLocalPosition() {
9783            measureContent();
9784            final int width = mContentView.getMeasuredWidth();
9785            final int offset = getTextOffset();
9786            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
9787            mPositionX += viewportToContentHorizontalOffset();
9788
9789            final int line = mLayout.getLineForOffset(offset);
9790            mPositionY = getVerticalLocalPosition(line);
9791            mPositionY += viewportToContentVerticalOffset();
9792        }
9793
9794        private void updatePosition(int parentPositionX, int parentPositionY) {
9795            int positionX = parentPositionX + mPositionX;
9796            int positionY = parentPositionY + mPositionY;
9797
9798            positionY = clipVertically(positionY);
9799
9800            // Horizontal clipping
9801            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9802            final int width = mContentView.getMeasuredWidth();
9803            positionX = Math.min(displayMetrics.widthPixels - width, positionX);
9804            positionX = Math.max(0, positionX);
9805
9806            if (isShowing()) {
9807                mPopupWindow.update(positionX, positionY, -1, -1);
9808            } else {
9809                mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
9810                        positionX, positionY);
9811            }
9812        }
9813
9814        public void hide() {
9815            mPopupWindow.dismiss();
9816            TextView.this.getPositionListener().removeSubscriber(this);
9817        }
9818
9819        @Override
9820        public void updatePosition(int parentPositionX, int parentPositionY,
9821                boolean parentPositionChanged, boolean parentScrolled) {
9822            // Either parentPositionChanged or parentScrolled is true, check if still visible
9823            if (isShowing() && isOffsetVisible(getTextOffset())) {
9824                if (parentScrolled) computeLocalPosition();
9825                updatePosition(parentPositionX, parentPositionY);
9826            } else {
9827                hide();
9828            }
9829        }
9830
9831        public boolean isShowing() {
9832            return mPopupWindow.isShowing();
9833        }
9834    }
9835
9836    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
9837        private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
9838        private static final int ADD_TO_DICTIONARY = -1;
9839        private static final int DELETE_TEXT = -2;
9840        private SuggestionInfo[] mSuggestionInfos;
9841        private int mNumberOfSuggestions;
9842        private boolean mCursorWasVisibleBeforeSuggestions;
9843        private boolean mIsShowingUp = false;
9844        private SuggestionAdapter mSuggestionsAdapter;
9845        private final Comparator<SuggestionSpan> mSuggestionSpanComparator;
9846        private final HashMap<SuggestionSpan, Integer> mSpansLengths;
9847
9848        private class CustomPopupWindow extends PopupWindow {
9849            public CustomPopupWindow(Context context, int defStyle) {
9850                super(context, null, defStyle);
9851            }
9852
9853            @Override
9854            public void dismiss() {
9855                super.dismiss();
9856
9857                TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
9858
9859                // Safe cast since show() checks that mText is an Editable
9860                ((Spannable) mText).removeSpan(getEditor().mSuggestionRangeSpan);
9861
9862                setCursorVisible(mCursorWasVisibleBeforeSuggestions);
9863                if (hasInsertionController()) {
9864                    getInsertionController().show();
9865                }
9866            }
9867        }
9868
9869        public SuggestionsPopupWindow() {
9870            mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
9871            mSuggestionSpanComparator = new SuggestionSpanComparator();
9872            mSpansLengths = new HashMap<SuggestionSpan, Integer>();
9873        }
9874
9875        @Override
9876        protected void createPopupWindow() {
9877            mPopupWindow = new CustomPopupWindow(TextView.this.mContext,
9878                com.android.internal.R.attr.textSuggestionsWindowStyle);
9879            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
9880            mPopupWindow.setFocusable(true);
9881            mPopupWindow.setClippingEnabled(false);
9882        }
9883
9884        @Override
9885        protected void initContentView() {
9886            ListView listView = new ListView(TextView.this.getContext());
9887            mSuggestionsAdapter = new SuggestionAdapter();
9888            listView.setAdapter(mSuggestionsAdapter);
9889            listView.setOnItemClickListener(this);
9890            mContentView = listView;
9891
9892            // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete
9893            mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2];
9894            for (int i = 0; i < mSuggestionInfos.length; i++) {
9895                mSuggestionInfos[i] = new SuggestionInfo();
9896            }
9897        }
9898
9899        public boolean isShowingUp() {
9900            return mIsShowingUp;
9901        }
9902
9903        public void onParentLostFocus() {
9904            mIsShowingUp = false;
9905        }
9906
9907        private class SuggestionInfo {
9908            int suggestionStart, suggestionEnd; // range of actual suggestion within text
9909            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
9910            int suggestionIndex; // the index of this suggestion inside suggestionSpan
9911            SpannableStringBuilder text = new SpannableStringBuilder();
9912            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
9913                    android.R.style.TextAppearance_SuggestionHighlight);
9914        }
9915
9916        private class SuggestionAdapter extends BaseAdapter {
9917            private LayoutInflater mInflater = (LayoutInflater) TextView.this.mContext.
9918                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9919
9920            @Override
9921            public int getCount() {
9922                return mNumberOfSuggestions;
9923            }
9924
9925            @Override
9926            public Object getItem(int position) {
9927                return mSuggestionInfos[position];
9928            }
9929
9930            @Override
9931            public long getItemId(int position) {
9932                return position;
9933            }
9934
9935            @Override
9936            public View getView(int position, View convertView, ViewGroup parent) {
9937                TextView textView = (TextView) convertView;
9938
9939                if (textView == null) {
9940                    textView = (TextView) mInflater.inflate(mTextEditSuggestionItemLayout, parent,
9941                            false);
9942                }
9943
9944                final SuggestionInfo suggestionInfo = mSuggestionInfos[position];
9945                textView.setText(suggestionInfo.text);
9946
9947                if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
9948                    textView.setCompoundDrawablesWithIntrinsicBounds(
9949                            com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0);
9950                } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
9951                    textView.setCompoundDrawablesWithIntrinsicBounds(
9952                            com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0);
9953                } else {
9954                    textView.setCompoundDrawables(null, null, null, null);
9955                }
9956
9957                return textView;
9958            }
9959        }
9960
9961        private class SuggestionSpanComparator implements Comparator<SuggestionSpan> {
9962            public int compare(SuggestionSpan span1, SuggestionSpan span2) {
9963                final int flag1 = span1.getFlags();
9964                final int flag2 = span2.getFlags();
9965                if (flag1 != flag2) {
9966                    // The order here should match what is used in updateDrawState
9967                    final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
9968                    final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
9969                    final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0;
9970                    final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0;
9971                    if (easy1 && !misspelled1) return -1;
9972                    if (easy2 && !misspelled2) return 1;
9973                    if (misspelled1) return -1;
9974                    if (misspelled2) return 1;
9975                }
9976
9977                return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue();
9978            }
9979        }
9980
9981        /**
9982         * Returns the suggestion spans that cover the current cursor position. The suggestion
9983         * spans are sorted according to the length of text that they are attached to.
9984         */
9985        private SuggestionSpan[] getSuggestionSpans() {
9986            int pos = TextView.this.getSelectionStart();
9987            Spannable spannable = (Spannable) TextView.this.mText;
9988            SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
9989
9990            mSpansLengths.clear();
9991            for (SuggestionSpan suggestionSpan : suggestionSpans) {
9992                int start = spannable.getSpanStart(suggestionSpan);
9993                int end = spannable.getSpanEnd(suggestionSpan);
9994                mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start));
9995            }
9996
9997            // The suggestions are sorted according to their types (easy correction first, then
9998            // misspelled) and to the length of the text that they cover (shorter first).
9999            Arrays.sort(suggestionSpans, mSuggestionSpanComparator);
10000            return suggestionSpans;
10001        }
10002
10003        @Override
10004        public void show() {
10005            if (!(mText instanceof Editable)) return;
10006
10007            if (updateSuggestions()) {
10008                mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
10009                setCursorVisible(false);
10010                mIsShowingUp = true;
10011                super.show();
10012            }
10013        }
10014
10015        @Override
10016        protected void measureContent() {
10017            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
10018            final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
10019                    displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
10020            final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
10021                    displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
10022
10023            int width = 0;
10024            View view = null;
10025            for (int i = 0; i < mNumberOfSuggestions; i++) {
10026                view = mSuggestionsAdapter.getView(i, view, mContentView);
10027                view.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
10028                view.measure(horizontalMeasure, verticalMeasure);
10029                width = Math.max(width, view.getMeasuredWidth());
10030            }
10031
10032            // Enforce the width based on actual text widths
10033            mContentView.measure(
10034                    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
10035                    verticalMeasure);
10036
10037            Drawable popupBackground = mPopupWindow.getBackground();
10038            if (popupBackground != null) {
10039                if (mTempRect == null) mTempRect = new Rect();
10040                popupBackground.getPadding(mTempRect);
10041                width += mTempRect.left + mTempRect.right;
10042            }
10043            mPopupWindow.setWidth(width);
10044        }
10045
10046        @Override
10047        protected int getTextOffset() {
10048            return getSelectionStart();
10049        }
10050
10051        @Override
10052        protected int getVerticalLocalPosition(int line) {
10053            return mLayout.getLineBottom(line);
10054        }
10055
10056        @Override
10057        protected int clipVertically(int positionY) {
10058            final int height = mContentView.getMeasuredHeight();
10059            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
10060            return Math.min(positionY, displayMetrics.heightPixels - height);
10061        }
10062
10063        @Override
10064        public void hide() {
10065            super.hide();
10066        }
10067
10068        private boolean updateSuggestions() {
10069            Spannable spannable = (Spannable) TextView.this.mText;
10070            SuggestionSpan[] suggestionSpans = getSuggestionSpans();
10071
10072            final int nbSpans = suggestionSpans.length;
10073            // Suggestions are shown after a delay: the underlying spans may have been removed
10074            if (nbSpans == 0) return false;
10075
10076            mNumberOfSuggestions = 0;
10077            int spanUnionStart = mText.length();
10078            int spanUnionEnd = 0;
10079
10080            SuggestionSpan misspelledSpan = null;
10081            int underlineColor = 0;
10082
10083            for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
10084                SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
10085                final int spanStart = spannable.getSpanStart(suggestionSpan);
10086                final int spanEnd = spannable.getSpanEnd(suggestionSpan);
10087                spanUnionStart = Math.min(spanStart, spanUnionStart);
10088                spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
10089
10090                if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
10091                    misspelledSpan = suggestionSpan;
10092                }
10093
10094                // The first span dictates the background color of the highlighted text
10095                if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor();
10096
10097                String[] suggestions = suggestionSpan.getSuggestions();
10098                int nbSuggestions = suggestions.length;
10099                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
10100                    String suggestion = suggestions[suggestionIndex];
10101
10102                    boolean suggestionIsDuplicate = false;
10103                    for (int i = 0; i < mNumberOfSuggestions; i++) {
10104                        if (mSuggestionInfos[i].text.toString().equals(suggestion)) {
10105                            SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan;
10106                            final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan);
10107                            final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan);
10108                            if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
10109                                suggestionIsDuplicate = true;
10110                                break;
10111                            }
10112                        }
10113                    }
10114
10115                    if (!suggestionIsDuplicate) {
10116                        SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
10117                        suggestionInfo.suggestionSpan = suggestionSpan;
10118                        suggestionInfo.suggestionIndex = suggestionIndex;
10119                        suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion);
10120
10121                        mNumberOfSuggestions++;
10122
10123                        if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
10124                            // Also end outer for loop
10125                            spanIndex = nbSpans;
10126                            break;
10127                        }
10128                    }
10129                }
10130            }
10131
10132            for (int i = 0; i < mNumberOfSuggestions; i++) {
10133                highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
10134            }
10135
10136            // Add "Add to dictionary" item if there is a span with the misspelled flag
10137            if (misspelledSpan != null) {
10138                final int misspelledStart = spannable.getSpanStart(misspelledSpan);
10139                final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
10140                if (misspelledStart >= 0 && misspelledEnd > misspelledStart) {
10141                    SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
10142                    suggestionInfo.suggestionSpan = misspelledSpan;
10143                    suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY;
10144                    suggestionInfo.text.replace(0, suggestionInfo.text.length(),
10145                            getContext().getString(com.android.internal.R.string.addToDictionary));
10146                    suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
10147                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10148
10149                    mNumberOfSuggestions++;
10150                }
10151            }
10152
10153            // Delete item
10154            SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
10155            suggestionInfo.suggestionSpan = null;
10156            suggestionInfo.suggestionIndex = DELETE_TEXT;
10157            suggestionInfo.text.replace(0, suggestionInfo.text.length(),
10158                    getContext().getString(com.android.internal.R.string.deleteText));
10159            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
10160                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10161            mNumberOfSuggestions++;
10162
10163            if (getEditor().mSuggestionRangeSpan == null) getEditor().mSuggestionRangeSpan = new SuggestionRangeSpan();
10164            if (underlineColor == 0) {
10165                // Fallback on the default highlight color when the first span does not provide one
10166                getEditor().mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
10167            } else {
10168                final float BACKGROUND_TRANSPARENCY = 0.4f;
10169                final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
10170                getEditor().mSuggestionRangeSpan.setBackgroundColor(
10171                        (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
10172            }
10173            spannable.setSpan(getEditor().mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
10174                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10175
10176            mSuggestionsAdapter.notifyDataSetChanged();
10177            return true;
10178        }
10179
10180        private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
10181                int unionEnd) {
10182            final Spannable text = (Spannable) mText;
10183            final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan);
10184            final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan);
10185
10186            // Adjust the start/end of the suggestion span
10187            suggestionInfo.suggestionStart = spanStart - unionStart;
10188            suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart
10189                    + suggestionInfo.text.length();
10190
10191            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0,
10192                    suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10193
10194            // Add the text before and after the span.
10195            final String textAsString = text.toString();
10196            suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart));
10197            suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd));
10198        }
10199
10200        @Override
10201        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
10202            Editable editable = (Editable) mText;
10203            SuggestionInfo suggestionInfo = mSuggestionInfos[position];
10204
10205            if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
10206                final int spanUnionStart = editable.getSpanStart(getEditor().mSuggestionRangeSpan);
10207                int spanUnionEnd = editable.getSpanEnd(getEditor().mSuggestionRangeSpan);
10208                if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) {
10209                    // Do not leave two adjacent spaces after deletion, or one at beginning of text
10210                    if (spanUnionEnd < editable.length() &&
10211                            Character.isSpaceChar(editable.charAt(spanUnionEnd)) &&
10212                            (spanUnionStart == 0 ||
10213                            Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) {
10214                        spanUnionEnd = spanUnionEnd + 1;
10215                    }
10216                    deleteText_internal(spanUnionStart, spanUnionEnd);
10217                }
10218                hide();
10219                return;
10220            }
10221
10222            final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan);
10223            final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
10224            if (spanStart < 0 || spanEnd <= spanStart) {
10225                // Span has been removed
10226                hide();
10227                return;
10228            }
10229            final String originalText = mText.toString().substring(spanStart, spanEnd);
10230
10231            if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
10232                Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
10233                intent.putExtra("word", originalText);
10234                intent.putExtra("locale", getTextServicesLocale().toString());
10235                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
10236                getContext().startActivity(intent);
10237                // There is no way to know if the word was indeed added. Re-check.
10238                // TODO The ExtractEditText should remove the span in the original text instead
10239                editable.removeSpan(suggestionInfo.suggestionSpan);
10240                updateSpellCheckSpans(spanStart, spanEnd, false);
10241            } else {
10242                // SuggestionSpans are removed by replace: save them before
10243                SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
10244                        SuggestionSpan.class);
10245                final int length = suggestionSpans.length;
10246                int[] suggestionSpansStarts = new int[length];
10247                int[] suggestionSpansEnds = new int[length];
10248                int[] suggestionSpansFlags = new int[length];
10249                for (int i = 0; i < length; i++) {
10250                    final SuggestionSpan suggestionSpan = suggestionSpans[i];
10251                    suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
10252                    suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
10253                    suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
10254
10255                    // Remove potential misspelled flags
10256                    int suggestionSpanFlags = suggestionSpan.getFlags();
10257                    if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
10258                        suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
10259                        suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
10260                        suggestionSpan.setFlags(suggestionSpanFlags);
10261                    }
10262                }
10263
10264                final int suggestionStart = suggestionInfo.suggestionStart;
10265                final int suggestionEnd = suggestionInfo.suggestionEnd;
10266                final String suggestion = suggestionInfo.text.subSequence(
10267                        suggestionStart, suggestionEnd).toString();
10268                replaceText_internal(spanStart, spanEnd, suggestion);
10269
10270                // Notify source IME of the suggestion pick. Do this before swaping texts.
10271                if (!TextUtils.isEmpty(
10272                        suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
10273                    InputMethodManager imm = InputMethodManager.peekInstance();
10274                    if (imm != null) {
10275                        imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
10276                                suggestionInfo.suggestionIndex);
10277                    }
10278                }
10279
10280                // Swap text content between actual text and Suggestion span
10281                String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
10282                suggestions[suggestionInfo.suggestionIndex] = originalText;
10283
10284                // Restore previous SuggestionSpans
10285                final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
10286                for (int i = 0; i < length; i++) {
10287                    // Only spans that include the modified region make sense after replacement
10288                    // Spans partially included in the replaced region are removed, there is no
10289                    // way to assign them a valid range after replacement
10290                    if (suggestionSpansStarts[i] <= spanStart &&
10291                            suggestionSpansEnds[i] >= spanEnd) {
10292                        setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i],
10293                                suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]);
10294                    }
10295                }
10296
10297                // Move cursor at the end of the replaced word
10298                final int newCursorPosition = spanEnd + lengthDifference;
10299                setCursorPosition_internal(newCursorPosition, newCursorPosition);
10300            }
10301
10302            hide();
10303        }
10304    }
10305
10306    /**
10307     * An ActionMode Callback class that is used to provide actions while in text selection mode.
10308     *
10309     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
10310     * on which of these this TextView supports.
10311     */
10312    private class SelectionActionModeCallback implements ActionMode.Callback {
10313
10314        @Override
10315        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
10316            TypedArray styledAttributes = mContext.obtainStyledAttributes(
10317                    com.android.internal.R.styleable.SelectionModeDrawables);
10318
10319            boolean allowText = getContext().getResources().getBoolean(
10320                    com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
10321
10322            mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
10323            mode.setSubtitle(null);
10324            mode.setTitleOptionalHint(true);
10325
10326            int selectAllIconId = 0; // No icon by default
10327            if (!allowText) {
10328                // Provide an icon, text will not be displayed on smaller screens.
10329                selectAllIconId = styledAttributes.getResourceId(
10330                        R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0);
10331            }
10332
10333            menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
10334                    setIcon(selectAllIconId).
10335                    setAlphabeticShortcut('a').
10336                    setShowAsAction(
10337                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10338
10339            if (canCut()) {
10340                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
10341                    setIcon(styledAttributes.getResourceId(
10342                            R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)).
10343                    setAlphabeticShortcut('x').
10344                    setShowAsAction(
10345                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10346            }
10347
10348            if (canCopy()) {
10349                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
10350                    setIcon(styledAttributes.getResourceId(
10351                            R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)).
10352                    setAlphabeticShortcut('c').
10353                    setShowAsAction(
10354                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10355            }
10356
10357            if (canPaste()) {
10358                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
10359                        setIcon(styledAttributes.getResourceId(
10360                                R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)).
10361                        setAlphabeticShortcut('v').
10362                        setShowAsAction(
10363                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10364            }
10365
10366            styledAttributes.recycle();
10367
10368            if (getEditor().mCustomSelectionActionModeCallback != null) {
10369                if (!getEditor().mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
10370                    // The custom mode can choose to cancel the action mode
10371                    return false;
10372                }
10373            }
10374
10375            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
10376                getSelectionController().show();
10377                return true;
10378            } else {
10379                return false;
10380            }
10381        }
10382
10383        @Override
10384        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
10385            if (getEditor().mCustomSelectionActionModeCallback != null) {
10386                return getEditor().mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
10387            }
10388            return true;
10389        }
10390
10391        @Override
10392        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
10393            if (getEditor().mCustomSelectionActionModeCallback != null &&
10394                 getEditor().mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
10395                return true;
10396            }
10397            return onTextContextMenuItem(item.getItemId());
10398        }
10399
10400        @Override
10401        public void onDestroyActionMode(ActionMode mode) {
10402            if (getEditor().mCustomSelectionActionModeCallback != null) {
10403                getEditor().mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
10404            }
10405            Selection.setSelection((Spannable) mText, getSelectionEnd());
10406
10407            if (getEditor().mSelectionModifierCursorController != null) {
10408                getEditor().mSelectionModifierCursorController.hide();
10409            }
10410
10411            getEditor().mSelectionActionMode = null;
10412        }
10413    }
10414
10415    private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
10416        private static final int POPUP_TEXT_LAYOUT =
10417                com.android.internal.R.layout.text_edit_action_popup_text;
10418        private TextView mPasteTextView;
10419        private TextView mReplaceTextView;
10420
10421        @Override
10422        protected void createPopupWindow() {
10423            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
10424                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10425            mPopupWindow.setClippingEnabled(true);
10426        }
10427
10428        @Override
10429        protected void initContentView() {
10430            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
10431            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
10432            mContentView = linearLayout;
10433            mContentView.setBackgroundResource(
10434                    com.android.internal.R.drawable.text_edit_paste_window);
10435
10436            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
10437                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
10438
10439            LayoutParams wrapContent = new LayoutParams(
10440                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
10441
10442            mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
10443            mPasteTextView.setLayoutParams(wrapContent);
10444            mContentView.addView(mPasteTextView);
10445            mPasteTextView.setText(com.android.internal.R.string.paste);
10446            mPasteTextView.setOnClickListener(this);
10447
10448            mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
10449            mReplaceTextView.setLayoutParams(wrapContent);
10450            mContentView.addView(mReplaceTextView);
10451            mReplaceTextView.setText(com.android.internal.R.string.replace);
10452            mReplaceTextView.setOnClickListener(this);
10453        }
10454
10455        @Override
10456        public void show() {
10457            boolean canPaste = canPaste();
10458            boolean canSuggest = isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
10459            mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE);
10460            mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE);
10461
10462            if (!canPaste && !canSuggest) return;
10463
10464            super.show();
10465        }
10466
10467        @Override
10468        public void onClick(View view) {
10469            if (view == mPasteTextView && canPaste()) {
10470                onTextContextMenuItem(ID_PASTE);
10471                hide();
10472            } else if (view == mReplaceTextView) {
10473                final int middle = (getSelectionStart() + getSelectionEnd()) / 2;
10474                stopSelectionActionMode();
10475                Selection.setSelection((Spannable) mText, middle);
10476                showSuggestions();
10477            }
10478        }
10479
10480        @Override
10481        protected int getTextOffset() {
10482            return (getSelectionStart() + getSelectionEnd()) / 2;
10483        }
10484
10485        @Override
10486        protected int getVerticalLocalPosition(int line) {
10487            return mLayout.getLineTop(line) - mContentView.getMeasuredHeight();
10488        }
10489
10490        @Override
10491        protected int clipVertically(int positionY) {
10492            if (positionY < 0) {
10493                final int offset = getTextOffset();
10494                final int line = mLayout.getLineForOffset(offset);
10495                positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line);
10496                positionY += mContentView.getMeasuredHeight();
10497
10498                // Assumes insertion and selection handles share the same height
10499                final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
10500                positionY += handle.getIntrinsicHeight();
10501            }
10502
10503            return positionY;
10504        }
10505    }
10506
10507    private abstract class HandleView extends View implements TextViewPositionListener {
10508        protected Drawable mDrawable;
10509        protected Drawable mDrawableLtr;
10510        protected Drawable mDrawableRtl;
10511        private final PopupWindow mContainer;
10512        // Position with respect to the parent TextView
10513        private int mPositionX, mPositionY;
10514        private boolean mIsDragging;
10515        // Offset from touch position to mPosition
10516        private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
10517        protected int mHotspotX;
10518        // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
10519        private float mTouchOffsetY;
10520        // Where the touch position should be on the handle to ensure a maximum cursor visibility
10521        private float mIdealVerticalOffset;
10522        // Parent's (TextView) previous position in window
10523        private int mLastParentX, mLastParentY;
10524        // Transient action popup window for Paste and Replace actions
10525        protected ActionPopupWindow mActionPopupWindow;
10526        // Previous text character offset
10527        private int mPreviousOffset = -1;
10528        // Previous text character offset
10529        private boolean mPositionHasChanged = true;
10530        // Used to delay the appearance of the action popup window
10531        private Runnable mActionPopupShower;
10532
10533        public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
10534            super(TextView.this.mContext);
10535            mContainer = new PopupWindow(TextView.this.mContext, null,
10536                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10537            mContainer.setSplitTouchEnabled(true);
10538            mContainer.setClippingEnabled(false);
10539            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
10540            mContainer.setContentView(this);
10541
10542            mDrawableLtr = drawableLtr;
10543            mDrawableRtl = drawableRtl;
10544
10545            updateDrawable();
10546
10547            final int handleHeight = mDrawable.getIntrinsicHeight();
10548            mTouchOffsetY = -0.3f * handleHeight;
10549            mIdealVerticalOffset = 0.7f * handleHeight;
10550        }
10551
10552        protected void updateDrawable() {
10553            final int offset = getCurrentCursorOffset();
10554            final boolean isRtlCharAtOffset = mLayout.isRtlCharAt(offset);
10555            mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
10556            mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
10557        }
10558
10559        protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
10560
10561        // Touch-up filter: number of previous positions remembered
10562        private static final int HISTORY_SIZE = 5;
10563        private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
10564        private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
10565        private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
10566        private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
10567        private int mPreviousOffsetIndex = 0;
10568        private int mNumberPreviousOffsets = 0;
10569
10570        private void startTouchUpFilter(int offset) {
10571            mNumberPreviousOffsets = 0;
10572            addPositionToTouchUpFilter(offset);
10573        }
10574
10575        private void addPositionToTouchUpFilter(int offset) {
10576            mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
10577            mPreviousOffsets[mPreviousOffsetIndex] = offset;
10578            mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
10579            mNumberPreviousOffsets++;
10580        }
10581
10582        private void filterOnTouchUp() {
10583            final long now = SystemClock.uptimeMillis();
10584            int i = 0;
10585            int index = mPreviousOffsetIndex;
10586            final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
10587            while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
10588                i++;
10589                index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
10590            }
10591
10592            if (i > 0 && i < iMax &&
10593                    (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
10594                positionAtCursorOffset(mPreviousOffsets[index], false);
10595            }
10596        }
10597
10598        public boolean offsetHasBeenChanged() {
10599            return mNumberPreviousOffsets > 1;
10600        }
10601
10602        @Override
10603        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
10604            setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
10605        }
10606
10607        public void show() {
10608            if (isShowing()) return;
10609
10610            getPositionListener().addSubscriber(this, true /* local position may change */);
10611
10612            // Make sure the offset is always considered new, even when focusing at same position
10613            mPreviousOffset = -1;
10614            positionAtCursorOffset(getCurrentCursorOffset(), false);
10615
10616            hideActionPopupWindow();
10617        }
10618
10619        protected void dismiss() {
10620            mIsDragging = false;
10621            mContainer.dismiss();
10622            onDetached();
10623        }
10624
10625        public void hide() {
10626            dismiss();
10627
10628            TextView.this.getPositionListener().removeSubscriber(this);
10629        }
10630
10631        void showActionPopupWindow(int delay) {
10632            if (mActionPopupWindow == null) {
10633                mActionPopupWindow = new ActionPopupWindow();
10634            }
10635            if (mActionPopupShower == null) {
10636                mActionPopupShower = new Runnable() {
10637                    public void run() {
10638                        mActionPopupWindow.show();
10639                    }
10640                };
10641            } else {
10642                TextView.this.removeCallbacks(mActionPopupShower);
10643            }
10644            TextView.this.postDelayed(mActionPopupShower, delay);
10645        }
10646
10647        protected void hideActionPopupWindow() {
10648            if (mActionPopupShower != null) {
10649                TextView.this.removeCallbacks(mActionPopupShower);
10650            }
10651            if (mActionPopupWindow != null) {
10652                mActionPopupWindow.hide();
10653            }
10654        }
10655
10656        public boolean isShowing() {
10657            return mContainer.isShowing();
10658        }
10659
10660        private boolean isVisible() {
10661            // Always show a dragging handle.
10662            if (mIsDragging) {
10663                return true;
10664            }
10665
10666            if (isInBatchEditMode()) {
10667                return false;
10668            }
10669
10670            return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY);
10671        }
10672
10673        public abstract int getCurrentCursorOffset();
10674
10675        protected abstract void updateSelection(int offset);
10676
10677        public abstract void updatePosition(float x, float y);
10678
10679        protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
10680            // A HandleView relies on the layout, which may be nulled by external methods
10681            if (mLayout == null) {
10682                // Will update controllers' state, hiding them and stopping selection mode if needed
10683                prepareCursorControllers();
10684                return;
10685            }
10686
10687            if (offset != mPreviousOffset || parentScrolled) {
10688                updateSelection(offset);
10689                addPositionToTouchUpFilter(offset);
10690                final int line = mLayout.getLineForOffset(offset);
10691
10692                mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
10693                mPositionY = mLayout.getLineBottom(line);
10694
10695                // Take TextView's padding and scroll into account.
10696                mPositionX += viewportToContentHorizontalOffset();
10697                mPositionY += viewportToContentVerticalOffset();
10698
10699                mPreviousOffset = offset;
10700                mPositionHasChanged = true;
10701            }
10702        }
10703
10704        public void updatePosition(int parentPositionX, int parentPositionY,
10705                boolean parentPositionChanged, boolean parentScrolled) {
10706            positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
10707            if (parentPositionChanged || mPositionHasChanged) {
10708                if (mIsDragging) {
10709                    // Update touchToWindow offset in case of parent scrolling while dragging
10710                    if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
10711                        mTouchToWindowOffsetX += parentPositionX - mLastParentX;
10712                        mTouchToWindowOffsetY += parentPositionY - mLastParentY;
10713                        mLastParentX = parentPositionX;
10714                        mLastParentY = parentPositionY;
10715                    }
10716
10717                    onHandleMoved();
10718                }
10719
10720                if (isVisible()) {
10721                    final int positionX = parentPositionX + mPositionX;
10722                    final int positionY = parentPositionY + mPositionY;
10723                    if (isShowing()) {
10724                        mContainer.update(positionX, positionY, -1, -1);
10725                    } else {
10726                        mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
10727                                positionX, positionY);
10728                    }
10729                } else {
10730                    if (isShowing()) {
10731                        dismiss();
10732                    }
10733                }
10734
10735                mPositionHasChanged = false;
10736            }
10737        }
10738
10739        @Override
10740        protected void onDraw(Canvas c) {
10741            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
10742            mDrawable.draw(c);
10743        }
10744
10745        @Override
10746        public boolean onTouchEvent(MotionEvent ev) {
10747            switch (ev.getActionMasked()) {
10748                case MotionEvent.ACTION_DOWN: {
10749                    startTouchUpFilter(getCurrentCursorOffset());
10750                    mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
10751                    mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
10752
10753                    final PositionListener positionListener = getPositionListener();
10754                    mLastParentX = positionListener.getPositionX();
10755                    mLastParentY = positionListener.getPositionY();
10756                    mIsDragging = true;
10757                    break;
10758                }
10759
10760                case MotionEvent.ACTION_MOVE: {
10761                    final float rawX = ev.getRawX();
10762                    final float rawY = ev.getRawY();
10763
10764                    // Vertical hysteresis: vertical down movement tends to snap to ideal offset
10765                    final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
10766                    final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
10767                    float newVerticalOffset;
10768                    if (previousVerticalOffset < mIdealVerticalOffset) {
10769                        newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
10770                        newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
10771                    } else {
10772                        newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
10773                        newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
10774                    }
10775                    mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
10776
10777                    final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
10778                    final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
10779
10780                    updatePosition(newPosX, newPosY);
10781                    break;
10782                }
10783
10784                case MotionEvent.ACTION_UP:
10785                    filterOnTouchUp();
10786                    mIsDragging = false;
10787                    break;
10788
10789                case MotionEvent.ACTION_CANCEL:
10790                    mIsDragging = false;
10791                    break;
10792            }
10793            return true;
10794        }
10795
10796        public boolean isDragging() {
10797            return mIsDragging;
10798        }
10799
10800        void onHandleMoved() {
10801            hideActionPopupWindow();
10802        }
10803
10804        public void onDetached() {
10805            hideActionPopupWindow();
10806        }
10807    }
10808
10809    private class InsertionHandleView extends HandleView {
10810        private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
10811        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
10812
10813        // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow
10814        private float mDownPositionX, mDownPositionY;
10815        private Runnable mHider;
10816
10817        public InsertionHandleView(Drawable drawable) {
10818            super(drawable, drawable);
10819        }
10820
10821        @Override
10822        public void show() {
10823            super.show();
10824
10825            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - LAST_CUT_OR_COPY_TIME;
10826            if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
10827                showActionPopupWindow(0);
10828            }
10829
10830            hideAfterDelay();
10831        }
10832
10833        public void showWithActionPopup() {
10834            show();
10835            showActionPopupWindow(0);
10836        }
10837
10838        private void hideAfterDelay() {
10839            if (mHider == null) {
10840                mHider = new Runnable() {
10841                    public void run() {
10842                        hide();
10843                    }
10844                };
10845            } else {
10846                removeHiderCallback();
10847            }
10848            TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
10849        }
10850
10851        private void removeHiderCallback() {
10852            if (mHider != null) {
10853                TextView.this.removeCallbacks(mHider);
10854            }
10855        }
10856
10857        @Override
10858        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10859            return drawable.getIntrinsicWidth() / 2;
10860        }
10861
10862        @Override
10863        public boolean onTouchEvent(MotionEvent ev) {
10864            final boolean result = super.onTouchEvent(ev);
10865
10866            switch (ev.getActionMasked()) {
10867                case MotionEvent.ACTION_DOWN:
10868                    mDownPositionX = ev.getRawX();
10869                    mDownPositionY = ev.getRawY();
10870                    break;
10871
10872                case MotionEvent.ACTION_UP:
10873                    if (!offsetHasBeenChanged()) {
10874                        final float deltaX = mDownPositionX - ev.getRawX();
10875                        final float deltaY = mDownPositionY - ev.getRawY();
10876                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
10877
10878                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
10879                                TextView.this.getContext());
10880                        final int touchSlop = viewConfiguration.getScaledTouchSlop();
10881
10882                        if (distanceSquared < touchSlop * touchSlop) {
10883                            if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
10884                                // Tapping on the handle dismisses the displayed action popup
10885                                mActionPopupWindow.hide();
10886                            } else {
10887                                showWithActionPopup();
10888                            }
10889                        }
10890                    }
10891                    hideAfterDelay();
10892                    break;
10893
10894                case MotionEvent.ACTION_CANCEL:
10895                    hideAfterDelay();
10896                    break;
10897
10898                default:
10899                    break;
10900            }
10901
10902            return result;
10903        }
10904
10905        @Override
10906        public int getCurrentCursorOffset() {
10907            return TextView.this.getSelectionStart();
10908        }
10909
10910        @Override
10911        public void updateSelection(int offset) {
10912            Selection.setSelection((Spannable) mText, offset);
10913        }
10914
10915        @Override
10916        public void updatePosition(float x, float y) {
10917            positionAtCursorOffset(getOffsetForPosition(x, y), false);
10918        }
10919
10920        @Override
10921        void onHandleMoved() {
10922            super.onHandleMoved();
10923            removeHiderCallback();
10924        }
10925
10926        @Override
10927        public void onDetached() {
10928            super.onDetached();
10929            removeHiderCallback();
10930        }
10931    }
10932
10933    private class SelectionStartHandleView extends HandleView {
10934
10935        public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
10936            super(drawableLtr, drawableRtl);
10937        }
10938
10939        @Override
10940        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10941            if (isRtlRun) {
10942                return drawable.getIntrinsicWidth() / 4;
10943            } else {
10944                return (drawable.getIntrinsicWidth() * 3) / 4;
10945            }
10946        }
10947
10948        @Override
10949        public int getCurrentCursorOffset() {
10950            return TextView.this.getSelectionStart();
10951        }
10952
10953        @Override
10954        public void updateSelection(int offset) {
10955            Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
10956            updateDrawable();
10957        }
10958
10959        @Override
10960        public void updatePosition(float x, float y) {
10961            int offset = getOffsetForPosition(x, y);
10962
10963            // Handles can not cross and selection is at least one character
10964            final int selectionEnd = getSelectionEnd();
10965            if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
10966
10967            positionAtCursorOffset(offset, false);
10968        }
10969
10970        public ActionPopupWindow getActionPopupWindow() {
10971            return mActionPopupWindow;
10972        }
10973    }
10974
10975    private class SelectionEndHandleView extends HandleView {
10976
10977        public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
10978            super(drawableLtr, drawableRtl);
10979        }
10980
10981        @Override
10982        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10983            if (isRtlRun) {
10984                return (drawable.getIntrinsicWidth() * 3) / 4;
10985            } else {
10986                return drawable.getIntrinsicWidth() / 4;
10987            }
10988        }
10989
10990        @Override
10991        public int getCurrentCursorOffset() {
10992            return TextView.this.getSelectionEnd();
10993        }
10994
10995        @Override
10996        public void updateSelection(int offset) {
10997            Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
10998            updateDrawable();
10999        }
11000
11001        @Override
11002        public void updatePosition(float x, float y) {
11003            int offset = getOffsetForPosition(x, y);
11004
11005            // Handles can not cross and selection is at least one character
11006            final int selectionStart = getSelectionStart();
11007            if (offset <= selectionStart) offset = Math.min(selectionStart + 1, mText.length());
11008
11009            positionAtCursorOffset(offset, false);
11010        }
11011
11012        public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
11013            mActionPopupWindow = actionPopupWindow;
11014        }
11015    }
11016
11017    /**
11018     * A CursorController instance can be used to control a cursor in the text.
11019     * It is not used outside of {@link TextView}.
11020     * @hide
11021     */
11022    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
11023        /**
11024         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
11025         * See also {@link #hide()}.
11026         */
11027        public void show();
11028
11029        /**
11030         * Hide the cursor controller from screen.
11031         * See also {@link #show()}.
11032         */
11033        public void hide();
11034
11035        /**
11036         * Called when the view is detached from window. Perform house keeping task, such as
11037         * stopping Runnable thread that would otherwise keep a reference on the context, thus
11038         * preventing the activity from being recycled.
11039         */
11040        public void onDetached();
11041    }
11042
11043    private class InsertionPointCursorController implements CursorController {
11044        private InsertionHandleView mHandle;
11045
11046        public void show() {
11047            getHandle().show();
11048        }
11049
11050        public void showWithActionPopup() {
11051            getHandle().showWithActionPopup();
11052        }
11053
11054        public void hide() {
11055            if (mHandle != null) {
11056                mHandle.hide();
11057            }
11058        }
11059
11060        public void onTouchModeChanged(boolean isInTouchMode) {
11061            if (!isInTouchMode) {
11062                hide();
11063            }
11064        }
11065
11066        private InsertionHandleView getHandle() {
11067            if (getEditor().mSelectHandleCenter == null) {
11068                getEditor().mSelectHandleCenter = mContext.getResources().getDrawable(
11069                        mTextSelectHandleRes);
11070            }
11071            if (mHandle == null) {
11072                mHandle = new InsertionHandleView(getEditor().mSelectHandleCenter);
11073            }
11074            return mHandle;
11075        }
11076
11077        @Override
11078        public void onDetached() {
11079            final ViewTreeObserver observer = getViewTreeObserver();
11080            observer.removeOnTouchModeChangeListener(this);
11081
11082            if (mHandle != null) mHandle.onDetached();
11083        }
11084    }
11085
11086    private class SelectionModifierCursorController implements CursorController {
11087        private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds
11088        // The cursor controller handles, lazily created when shown.
11089        private SelectionStartHandleView mStartHandle;
11090        private SelectionEndHandleView mEndHandle;
11091        // The offsets of that last touch down event. Remembered to start selection there.
11092        private int mMinTouchOffset, mMaxTouchOffset;
11093
11094        // Double tap detection
11095        private long mPreviousTapUpTime = 0;
11096        private float mDownPositionX, mDownPositionY;
11097        private boolean mGestureStayedInTapRegion;
11098
11099        SelectionModifierCursorController() {
11100            resetTouchOffsets();
11101        }
11102
11103        public void show() {
11104            if (isInBatchEditMode()) {
11105                return;
11106            }
11107            initDrawables();
11108            initHandles();
11109            hideInsertionPointCursorController();
11110        }
11111
11112        private void initDrawables() {
11113            if (getEditor().mSelectHandleLeft == null) {
11114                getEditor().mSelectHandleLeft = mContext.getResources().getDrawable(
11115                        mTextSelectHandleLeftRes);
11116            }
11117            if (getEditor().mSelectHandleRight == null) {
11118                getEditor().mSelectHandleRight = mContext.getResources().getDrawable(
11119                        mTextSelectHandleRightRes);
11120            }
11121        }
11122
11123        private void initHandles() {
11124            // Lazy object creation has to be done before updatePosition() is called.
11125            if (mStartHandle == null) {
11126                mStartHandle = new SelectionStartHandleView(getEditor().mSelectHandleLeft, getEditor().mSelectHandleRight);
11127            }
11128            if (mEndHandle == null) {
11129                mEndHandle = new SelectionEndHandleView(getEditor().mSelectHandleRight, getEditor().mSelectHandleLeft);
11130            }
11131
11132            mStartHandle.show();
11133            mEndHandle.show();
11134
11135            // Make sure both left and right handles share the same ActionPopupWindow (so that
11136            // moving any of the handles hides the action popup).
11137            mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION);
11138            mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
11139
11140            hideInsertionPointCursorController();
11141        }
11142
11143        public void hide() {
11144            if (mStartHandle != null) mStartHandle.hide();
11145            if (mEndHandle != null) mEndHandle.hide();
11146        }
11147
11148        public void onTouchEvent(MotionEvent event) {
11149            // This is done even when the View does not have focus, so that long presses can start
11150            // selection and tap can move cursor from this tap position.
11151            switch (event.getActionMasked()) {
11152                case MotionEvent.ACTION_DOWN:
11153                    final float x = event.getX();
11154                    final float y = event.getY();
11155
11156                    // Remember finger down position, to be able to start selection from there
11157                    mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
11158
11159                    // Double tap detection
11160                    if (mGestureStayedInTapRegion) {
11161                        long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
11162                        if (duration <= ViewConfiguration.getDoubleTapTimeout()) {
11163                            final float deltaX = x - mDownPositionX;
11164                            final float deltaY = y - mDownPositionY;
11165                            final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
11166
11167                            ViewConfiguration viewConfiguration = ViewConfiguration.get(
11168                                    TextView.this.getContext());
11169                            int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
11170                            boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop;
11171
11172                            if (stayedInArea && isPositionOnText(x, y)) {
11173                                startSelectionActionMode();
11174                                getEditor().mDiscardNextActionUp = true;
11175                            }
11176                        }
11177                    }
11178
11179                    mDownPositionX = x;
11180                    mDownPositionY = y;
11181                    mGestureStayedInTapRegion = true;
11182                    break;
11183
11184                case MotionEvent.ACTION_POINTER_DOWN:
11185                case MotionEvent.ACTION_POINTER_UP:
11186                    // Handle multi-point gestures. Keep min and max offset positions.
11187                    // Only activated for devices that correctly handle multi-touch.
11188                    if (mContext.getPackageManager().hasSystemFeature(
11189                            PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
11190                        updateMinAndMaxOffsets(event);
11191                    }
11192                    break;
11193
11194                case MotionEvent.ACTION_MOVE:
11195                    if (mGestureStayedInTapRegion) {
11196                        final float deltaX = event.getX() - mDownPositionX;
11197                        final float deltaY = event.getY() - mDownPositionY;
11198                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
11199
11200                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
11201                                TextView.this.getContext());
11202                        int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
11203
11204                        if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
11205                            mGestureStayedInTapRegion = false;
11206                        }
11207                    }
11208                    break;
11209
11210                case MotionEvent.ACTION_UP:
11211                    mPreviousTapUpTime = SystemClock.uptimeMillis();
11212                    break;
11213            }
11214        }
11215
11216        /**
11217         * @param event
11218         */
11219        private void updateMinAndMaxOffsets(MotionEvent event) {
11220            int pointerCount = event.getPointerCount();
11221            for (int index = 0; index < pointerCount; index++) {
11222                int offset = getOffsetForPosition(event.getX(index), event.getY(index));
11223                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
11224                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
11225            }
11226        }
11227
11228        public int getMinTouchOffset() {
11229            return mMinTouchOffset;
11230        }
11231
11232        public int getMaxTouchOffset() {
11233            return mMaxTouchOffset;
11234        }
11235
11236        public void resetTouchOffsets() {
11237            mMinTouchOffset = mMaxTouchOffset = -1;
11238        }
11239
11240        /**
11241         * @return true iff this controller is currently used to move the selection start.
11242         */
11243        public boolean isSelectionStartDragged() {
11244            return mStartHandle != null && mStartHandle.isDragging();
11245        }
11246
11247        public void onTouchModeChanged(boolean isInTouchMode) {
11248            if (!isInTouchMode) {
11249                hide();
11250            }
11251        }
11252
11253        @Override
11254        public void onDetached() {
11255            final ViewTreeObserver observer = getViewTreeObserver();
11256            observer.removeOnTouchModeChangeListener(this);
11257
11258            if (mStartHandle != null) mStartHandle.onDetached();
11259            if (mEndHandle != null) mEndHandle.onDetached();
11260        }
11261    }
11262
11263    static class InputContentType {
11264        int imeOptions = EditorInfo.IME_NULL;
11265        String privateImeOptions;
11266        CharSequence imeActionLabel;
11267        int imeActionId;
11268        Bundle extras;
11269        OnEditorActionListener onEditorActionListener;
11270        boolean enterDown;
11271    }
11272
11273    static class InputMethodState {
11274        Rect mCursorRectInWindow = new Rect();
11275        RectF mTmpRectF = new RectF();
11276        float[] mTmpOffset = new float[2];
11277        ExtractedTextRequest mExtracting;
11278        final ExtractedText mTmpExtracted = new ExtractedText();
11279        int mBatchEditNesting;
11280        boolean mCursorChanged;
11281        boolean mSelectionModeChanged;
11282        boolean mContentChanged;
11283        int mChangedStart, mChangedEnd, mChangedDelta;
11284    }
11285
11286    private class Editor {
11287        // Cursor Controllers.
11288        InsertionPointCursorController mInsertionPointCursorController;
11289        SelectionModifierCursorController mSelectionModifierCursorController;
11290        ActionMode mSelectionActionMode;
11291        boolean mInsertionControllerEnabled;
11292        boolean mSelectionControllerEnabled;
11293
11294        // Used to highlight a word when it is corrected by the IME
11295        CorrectionHighlighter mCorrectionHighlighter;
11296
11297        InputContentType mInputContentType;
11298        InputMethodState mInputMethodState;
11299
11300        DisplayList mTextDisplayList;
11301        boolean mTextDisplayListIsValid;
11302
11303        boolean mFrozenWithFocus;
11304        boolean mSelectionMoved;
11305        boolean mTouchFocusSelected;
11306
11307        KeyListener mKeyListener;
11308        int mInputType = EditorInfo.TYPE_NULL;
11309
11310        boolean mDiscardNextActionUp;
11311        boolean mIgnoreActionUpEvent;
11312
11313        long mShowCursor;
11314        Blink mBlink;
11315
11316        boolean mCursorVisible = true;
11317        boolean mSelectAllOnFocus;
11318        boolean mTextIsSelectable;
11319
11320        CharSequence mError;
11321        boolean mErrorWasChanged;
11322        ErrorPopup mErrorPopup;
11323        /**
11324         * This flag is set if the TextView tries to display an error before it
11325         * is attached to the window (so its position is still unknown).
11326         * It causes the error to be shown later, when onAttachedToWindow()
11327         * is called.
11328         */
11329        boolean mShowErrorAfterAttach;
11330
11331        boolean mInBatchEditControllers;
11332
11333        SuggestionsPopupWindow mSuggestionsPopupWindow;
11334        SuggestionRangeSpan mSuggestionRangeSpan;
11335        Runnable mShowSuggestionRunnable;
11336
11337        final Drawable[] mCursorDrawable = new Drawable[2];
11338        int mCursorCount; // Current number of used mCursorDrawable: 0 (resource=0), 1 or 2 (split)
11339
11340        Drawable mSelectHandleLeft;
11341        Drawable mSelectHandleRight;
11342        Drawable mSelectHandleCenter;
11343
11344        // Global listener that detects changes in the global position of the TextView
11345        PositionListener mPositionListener;
11346
11347        float mLastDownPositionX, mLastDownPositionY;
11348        Callback mCustomSelectionActionModeCallback;
11349
11350        // Set when this TextView gained focus with some text selected. Will start selection mode.
11351        boolean mCreatedWithASelection;
11352
11353        WordIterator mWordIterator;
11354        SpellChecker mSpellChecker;
11355
11356        void onAttachedToWindow() {
11357            final ViewTreeObserver observer = getViewTreeObserver();
11358            // No need to create the controller.
11359            // The get method will add the listener on controller creation.
11360            if (mInsertionPointCursorController != null) {
11361                observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
11362            }
11363            if (mSelectionModifierCursorController != null) {
11364                observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
11365            }
11366            updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
11367        }
11368
11369        void onDetachedFromWindow() {
11370            if (mError != null) {
11371                hideError();
11372            }
11373
11374            if (mBlink != null) {
11375                mBlink.removeCallbacks(mBlink);
11376            }
11377
11378            if (mInsertionPointCursorController != null) {
11379                mInsertionPointCursorController.onDetached();
11380            }
11381
11382            if (mSelectionModifierCursorController != null) {
11383                mSelectionModifierCursorController.onDetached();
11384            }
11385
11386            if (mShowSuggestionRunnable != null) {
11387                removeCallbacks(mShowSuggestionRunnable);
11388            }
11389
11390            if (mTextDisplayList != null) {
11391                mTextDisplayList.invalidate();
11392            }
11393
11394            if (mSpellChecker != null) {
11395                mSpellChecker.closeSession();
11396                // Forces the creation of a new SpellChecker next time this window is created.
11397                // Will handle the cases where the settings has been changed in the meantime.
11398                mSpellChecker = null;
11399            }
11400
11401            hideControllers();
11402        }
11403
11404        void adjustInputType(boolean password, boolean passwordInputType,
11405                boolean webPasswordInputType, boolean numberPasswordInputType) {
11406            // mInputType has been set from inputType, possibly modified by mInputMethod.
11407            // Specialize mInputType to [web]password if we have a text class and the original input
11408            // type was a password.
11409            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
11410                if (password || passwordInputType) {
11411                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
11412                            | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
11413                }
11414                if (webPasswordInputType) {
11415                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
11416                            | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
11417                }
11418            } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
11419                if (numberPasswordInputType) {
11420                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
11421                            | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
11422                }
11423            }
11424        }
11425
11426        void setFrame() {
11427            if (mErrorPopup != null) {
11428                TextView tv = (TextView) mErrorPopup.getContentView();
11429                chooseSize(mErrorPopup, mError, tv);
11430                mErrorPopup.update(TextView.this, getErrorX(), getErrorY(),
11431                        mErrorPopup.getWidth(), mErrorPopup.getHeight());
11432            }
11433        }
11434
11435        void onFocusChanged(boolean focused, int direction) {
11436            mShowCursor = SystemClock.uptimeMillis();
11437            ensureEndedBatchEdit();
11438
11439            if (focused) {
11440                int selStart = getSelectionStart();
11441                int selEnd = getSelectionEnd();
11442
11443                // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
11444                // mode for these, unless there was a specific selection already started.
11445                final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
11446                        selEnd == mText.length();
11447
11448                mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
11449
11450                if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
11451                    // If a tap was used to give focus to that view, move cursor at tap position.
11452                    // Has to be done before onTakeFocus, which can be overloaded.
11453                    final int lastTapPosition = getLastTapPosition();
11454                    if (lastTapPosition >= 0) {
11455                        Selection.setSelection((Spannable) mText, lastTapPosition);
11456                    }
11457
11458                    // Note this may have to be moved out of the Editor class
11459                    if (mMovement != null) {
11460                        mMovement.onTakeFocus(TextView.this, (Spannable) mText, direction);
11461                    }
11462
11463                    // The DecorView does not have focus when the 'Done' ExtractEditText button is
11464                    // pressed. Since it is the ViewAncestor's mView, it requests focus before
11465                    // ExtractEditText clears focus, which gives focus to the ExtractEditText.
11466                    // This special case ensure that we keep current selection in that case.
11467                    // It would be better to know why the DecorView does not have focus at that time.
11468                    if (((TextView.this instanceof ExtractEditText) || mSelectionMoved) &&
11469                            selStart >= 0 && selEnd >= 0) {
11470                        /*
11471                         * Someone intentionally set the selection, so let them
11472                         * do whatever it is that they wanted to do instead of
11473                         * the default on-focus behavior.  We reset the selection
11474                         * here instead of just skipping the onTakeFocus() call
11475                         * because some movement methods do something other than
11476                         * just setting the selection in theirs and we still
11477                         * need to go through that path.
11478                         */
11479                        Selection.setSelection((Spannable) mText, selStart, selEnd);
11480                    }
11481
11482                    if (mSelectAllOnFocus) {
11483                        selectAll();
11484                    }
11485
11486                    mTouchFocusSelected = true;
11487                }
11488
11489                mFrozenWithFocus = false;
11490                mSelectionMoved = false;
11491
11492                if (mError != null) {
11493                    showError();
11494                }
11495
11496                makeBlink();
11497            } else {
11498                if (mError != null) {
11499                    hideError();
11500                }
11501                // Don't leave us in the middle of a batch edit.
11502                onEndBatchEdit();
11503
11504                if (TextView.this instanceof ExtractEditText) {
11505                    // terminateTextSelectionMode removes selection, which we want to keep when
11506                    // ExtractEditText goes out of focus.
11507                    final int selStart = getSelectionStart();
11508                    final int selEnd = getSelectionEnd();
11509                    hideControllers();
11510                    Selection.setSelection((Spannable) mText, selStart, selEnd);
11511                } else {
11512                    hideControllers();
11513                    downgradeEasyCorrectionSpans();
11514                }
11515
11516                // No need to create the controller
11517                if (mSelectionModifierCursorController != null) {
11518                    mSelectionModifierCursorController.resetTouchOffsets();
11519                }
11520            }
11521        }
11522
11523        void sendOnTextChanged(int start, int after) {
11524            updateSpellCheckSpans(start, start + after, false);
11525            mTextDisplayListIsValid = false;
11526
11527            // Hide the controllers as soon as text is modified (typing, procedural...)
11528            // We do not hide the span controllers, since they can be added when a new text is
11529            // inserted into the text view (voice IME).
11530            hideCursorControllers();
11531        }
11532
11533        private int getLastTapPosition() {
11534            // No need to create the controller at that point, no last tap position saved
11535            if (mSelectionModifierCursorController != null) {
11536                int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
11537                if (lastTapPosition >= 0) {
11538                    // Safety check, should not be possible.
11539                    if (lastTapPosition > mText.length()) {
11540                        Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
11541                                + mText.length() + ")");
11542                        lastTapPosition = mText.length();
11543                    }
11544                    return lastTapPosition;
11545                }
11546            }
11547
11548            return -1;
11549        }
11550
11551        void onWindowFocusChanged(boolean hasWindowFocus) {
11552            if (hasWindowFocus) {
11553                if (mBlink != null) {
11554                    mBlink.uncancel();
11555                    makeBlink();
11556                }
11557            } else {
11558                if (mBlink != null) {
11559                    mBlink.cancel();
11560                }
11561                if (mInputContentType != null) {
11562                    mInputContentType.enterDown = false;
11563                }
11564                // Order matters! Must be done before onParentLostFocus to rely on isShowingUp
11565                hideControllers();
11566                if (mSuggestionsPopupWindow != null) {
11567                    mSuggestionsPopupWindow.onParentLostFocus();
11568                }
11569
11570                // Don't leave us in the middle of a batch edit.
11571                onEndBatchEdit();
11572            }
11573        }
11574
11575        void onTouchEvent(MotionEvent event) {
11576            if (hasSelectionController()) {
11577                getSelectionController().onTouchEvent(event);
11578            }
11579
11580            if (mShowSuggestionRunnable != null) {
11581                removeCallbacks(mShowSuggestionRunnable);
11582                mShowSuggestionRunnable = null;
11583            }
11584
11585            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
11586                mLastDownPositionX = event.getX();
11587                mLastDownPositionY = event.getY();
11588
11589                // Reset this state; it will be re-set if super.onTouchEvent
11590                // causes focus to move to the view.
11591                mTouchFocusSelected = false;
11592                mIgnoreActionUpEvent = false;
11593            }
11594        }
11595
11596        void onDraw(Canvas canvas, Layout layout, Path highlight, int cursorOffsetVertical) {
11597            final int selectionStart = getSelectionStart();
11598            final int selectionEnd = getSelectionEnd();
11599
11600            final InputMethodState ims = mInputMethodState;
11601            if (ims != null && ims.mBatchEditNesting == 0) {
11602                InputMethodManager imm = InputMethodManager.peekInstance();
11603                if (imm != null) {
11604                    if (imm.isActive(TextView.this)) {
11605                        boolean reported = false;
11606                        if (ims.mContentChanged || ims.mSelectionModeChanged) {
11607                            // We are in extract mode and the content has changed
11608                            // in some way... just report complete new text to the
11609                            // input method.
11610                            reported = reportExtractedText();
11611                        }
11612                        if (!reported && highlight != null) {
11613                            int candStart = -1;
11614                            int candEnd = -1;
11615                            if (mText instanceof Spannable) {
11616                                Spannable sp = (Spannable)mText;
11617                                candStart = EditableInputConnection.getComposingSpanStart(sp);
11618                                candEnd = EditableInputConnection.getComposingSpanEnd(sp);
11619                            }
11620                            imm.updateSelection(TextView.this,
11621                                    selectionStart, selectionEnd, candStart, candEnd);
11622                        }
11623                    }
11624
11625                    if (imm.isWatchingCursor(TextView.this) && highlight != null) {
11626                        highlight.computeBounds(ims.mTmpRectF, true);
11627                        ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
11628
11629                        canvas.getMatrix().mapPoints(ims.mTmpOffset);
11630                        ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
11631
11632                        ims.mTmpRectF.offset(0, cursorOffsetVertical);
11633
11634                        ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
11635                                (int)(ims.mTmpRectF.top + 0.5),
11636                                (int)(ims.mTmpRectF.right + 0.5),
11637                                (int)(ims.mTmpRectF.bottom + 0.5));
11638
11639                        imm.updateCursor(TextView.this,
11640                                ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
11641                                ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
11642                    }
11643                }
11644            }
11645
11646            if (mCorrectionHighlighter != null) {
11647                mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
11648            }
11649
11650            if (highlight != null && selectionStart == selectionEnd && mCursorCount > 0) {
11651                drawCursor(canvas, cursorOffsetVertical);
11652                // Rely on the drawable entirely, do not draw the cursor line.
11653                // Has to be done after the IMM related code above which relies on the highlight.
11654                highlight = null;
11655            }
11656
11657            if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
11658                final int width = mRight - mLeft;
11659                final int height = mBottom - mTop;
11660
11661                if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
11662                        !mTextDisplayListIsValid) {
11663                    if (mTextDisplayList == null) {
11664                        mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
11665                    }
11666
11667                    final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
11668                    try {
11669                        hardwareCanvas.setViewport(width, height);
11670                        // The dirty rect should always be null for a display list
11671                        hardwareCanvas.onPreDraw(null);
11672                        hardwareCanvas.translate(-mScrollX, -mScrollY);
11673                        layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
11674                        hardwareCanvas.translate(mScrollX, mScrollY);
11675                    } finally {
11676                        hardwareCanvas.onPostDraw();
11677                        mTextDisplayList.end();
11678                        mTextDisplayListIsValid = true;
11679                    }
11680                }
11681                canvas.translate(mScrollX, mScrollY);
11682                ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
11683                        DisplayList.FLAG_CLIP_CHILDREN);
11684                canvas.translate(-mScrollX, -mScrollY);
11685            } else {
11686                layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
11687            }
11688
11689            if (mMarquee != null && mMarquee.shouldDrawGhost()) {
11690                canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
11691                layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
11692            }
11693        }
11694
11695        private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
11696            final boolean translate = cursorOffsetVertical != 0;
11697            if (translate) canvas.translate(0, cursorOffsetVertical);
11698            for (int i = 0; i < getEditor().mCursorCount; i++) {
11699                mCursorDrawable[i].draw(canvas);
11700            }
11701            if (translate) canvas.translate(0, -cursorOffsetVertical);
11702        }
11703
11704        private void updateCursorsPositions() {
11705            if (mCursorDrawableRes == 0) {
11706                mCursorCount = 0;
11707                return;
11708            }
11709
11710            final int offset = getSelectionStart();
11711            final int line = mLayout.getLineForOffset(offset);
11712            final int top = mLayout.getLineTop(line);
11713            final int bottom = mLayout.getLineTop(line + 1);
11714
11715            mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
11716
11717            int middle = bottom;
11718            if (mCursorCount == 2) {
11719                // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
11720                middle = (top + bottom) >> 1;
11721            }
11722
11723            updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
11724
11725            if (mCursorCount == 2) {
11726                updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
11727            }
11728        }
11729
11730        private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
11731            if (mCursorDrawable[cursorIndex] == null)
11732                mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
11733
11734            if (mTempRect == null) mTempRect = new Rect();
11735            mCursorDrawable[cursorIndex].getPadding(mTempRect);
11736            final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
11737            horizontal = Math.max(0.5f, horizontal - 0.5f);
11738            final int left = (int) (horizontal) - mTempRect.left;
11739            mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
11740                    bottom + mTempRect.bottom);
11741        }
11742    }
11743}
11744