TextView.java revision 6382ade0c7e26c88c4a17f7ee7124ed92b0b8bcc
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) {
8280            if (getEditor().mPositionListener != null) {
8281                getEditor().mPositionListener.onScrollChanged();
8282            }
8283            getEditor().mTextDisplayListIsValid = false;
8284        }
8285    }
8286
8287    /**
8288     * Removes the suggestion spans.
8289     */
8290    CharSequence removeSuggestionSpans(CharSequence text) {
8291       if (text instanceof Spanned) {
8292           Spannable spannable;
8293           if (text instanceof Spannable) {
8294               spannable = (Spannable) text;
8295           } else {
8296               spannable = new SpannableString(text);
8297               text = spannable;
8298           }
8299
8300           SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
8301           for (int i = 0; i < spans.length; i++) {
8302               spannable.removeSpan(spans[i]);
8303           }
8304       }
8305       return text;
8306    }
8307
8308    void showSuggestions() {
8309        if (getEditor().mSuggestionsPopupWindow == null) {
8310            getEditor().mSuggestionsPopupWindow = new SuggestionsPopupWindow();
8311        }
8312        hideControllers();
8313        getEditor().mSuggestionsPopupWindow.show();
8314    }
8315
8316    boolean areSuggestionsShown() {
8317        return getEditor().mSuggestionsPopupWindow != null && getEditor().mSuggestionsPopupWindow.isShowing();
8318    }
8319
8320    /**
8321     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8322     * by the IME or by the spell checker as the user types. This is done by adding
8323     * {@link SuggestionSpan}s to the text.
8324     *
8325     * When suggestions are enabled (default), this list of suggestions will be displayed when the
8326     * user asks for them on these parts of the text. This value depends on the inputType of this
8327     * TextView.
8328     *
8329     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8330     *
8331     * In addition, the type variation must be one of
8332     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8333     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8334     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8335     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8336     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8337     *
8338     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8339     *
8340     * @return true if the suggestions popup window is enabled, based on the inputType.
8341     */
8342    public boolean isSuggestionsEnabled() {
8343        if (mEditor == null) return false;
8344        if ((getEditor().mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
8345        if ((getEditor().mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
8346
8347        final int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
8348        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8349                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8350                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8351                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8352                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8353    }
8354
8355    /**
8356     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8357     * selection is initiated in this View.
8358     *
8359     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8360     * Paste actions, depending on what this View supports.
8361     *
8362     * A custom implementation can add new entries in the default menu in its
8363     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8364     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8365     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8366     * or {@link android.R.id#paste} ids as parameters.
8367     *
8368     * Returning false from
8369     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8370     * the action mode from being started.
8371     *
8372     * Action click events should be handled by the custom implementation of
8373     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8374     *
8375     * Note that text selection mode is not started when a TextView receives focus and the
8376     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8377     * that case, to allow for quick replacement.
8378     */
8379    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8380        createEditorIfNeeded("custom selection action mode set");
8381        getEditor().mCustomSelectionActionModeCallback = actionModeCallback;
8382    }
8383
8384    /**
8385     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8386     *
8387     * @return The current custom selection callback.
8388     */
8389    public ActionMode.Callback getCustomSelectionActionModeCallback() {
8390        return mEditor == null ? null : getEditor().mCustomSelectionActionModeCallback;
8391    }
8392
8393    /**
8394     *
8395     * @return true if the selection mode was actually started.
8396     */
8397    private boolean startSelectionActionMode() {
8398        if (getEditor().mSelectionActionMode != null) {
8399            // Selection action mode is already started
8400            return false;
8401        }
8402
8403        if (!canSelectText() || !requestFocus()) {
8404            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
8405            return false;
8406        }
8407
8408        if (!hasSelection()) {
8409            // There may already be a selection on device rotation
8410            if (!selectCurrentWord()) {
8411                // No word found under cursor or text selection not permitted.
8412                return false;
8413            }
8414        }
8415
8416        boolean willExtract = extractedTextModeWillBeStarted();
8417
8418        // Do not start the action mode when extracted text will show up full screen, which would
8419        // immediately hide the newly created action bar and would be visually distracting.
8420        if (!willExtract) {
8421            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
8422            getEditor().mSelectionActionMode = startActionMode(actionModeCallback);
8423        }
8424
8425        final boolean selectionStarted = getEditor().mSelectionActionMode != null || willExtract;
8426        if (selectionStarted && !isTextSelectable()) {
8427            // Show the IME to be able to replace text, except when selecting non editable text.
8428            final InputMethodManager imm = InputMethodManager.peekInstance();
8429            if (imm != null) {
8430                imm.showSoftInput(this, 0, null);
8431            }
8432        }
8433
8434        return selectionStarted;
8435    }
8436
8437    private boolean extractedTextModeWillBeStarted() {
8438        if (!(this instanceof ExtractEditText)) {
8439            final InputMethodManager imm = InputMethodManager.peekInstance();
8440            return  imm != null && imm.isFullscreenMode();
8441        }
8442        return false;
8443    }
8444
8445    /**
8446     * @hide
8447     */
8448    protected void stopSelectionActionMode() {
8449        if (getEditor().mSelectionActionMode != null) {
8450            // This will hide the mSelectionModifierCursorController
8451            getEditor().mSelectionActionMode.finish();
8452        }
8453    }
8454
8455    /**
8456     * Paste clipboard content between min and max positions.
8457     */
8458    private void paste(int min, int max) {
8459        ClipboardManager clipboard =
8460            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8461        ClipData clip = clipboard.getPrimaryClip();
8462        if (clip != null) {
8463            boolean didFirst = false;
8464            for (int i=0; i<clip.getItemCount(); i++) {
8465                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
8466                if (paste != null) {
8467                    if (!didFirst) {
8468                        long minMax = prepareSpacesAroundPaste(min, max, paste);
8469                        min = extractRangeStartFromLong(minMax);
8470                        max = extractRangeEndFromLong(minMax);
8471                        Selection.setSelection((Spannable) mText, max);
8472                        ((Editable) mText).replace(min, max, paste);
8473                        didFirst = true;
8474                    } else {
8475                        ((Editable) mText).insert(getSelectionEnd(), "\n");
8476                        ((Editable) mText).insert(getSelectionEnd(), paste);
8477                    }
8478                }
8479            }
8480            stopSelectionActionMode();
8481            LAST_CUT_OR_COPY_TIME = 0;
8482        }
8483    }
8484
8485    private void setPrimaryClip(ClipData clip) {
8486        ClipboardManager clipboard = (ClipboardManager) getContext().
8487                getSystemService(Context.CLIPBOARD_SERVICE);
8488        clipboard.setPrimaryClip(clip);
8489        LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8490    }
8491
8492    private void hideInsertionPointCursorController() {
8493        // No need to create the controller to hide it.
8494        if (getEditor().mInsertionPointCursorController != null) {
8495            getEditor().mInsertionPointCursorController.hide();
8496        }
8497    }
8498
8499    /**
8500     * Hides the insertion controller and stops text selection mode, hiding the selection controller
8501     */
8502    private void hideControllers() {
8503        hideCursorControllers();
8504        hideSpanControllers();
8505    }
8506
8507    private void hideSpanControllers() {
8508        if (mChangeWatcher != null) {
8509            mChangeWatcher.hideControllers();
8510        }
8511    }
8512
8513    private void hideCursorControllers() {
8514        if (getEditor().mSuggestionsPopupWindow != null && !getEditor().mSuggestionsPopupWindow.isShowingUp()) {
8515            // Should be done before hide insertion point controller since it triggers a show of it
8516            getEditor().mSuggestionsPopupWindow.hide();
8517        }
8518        hideInsertionPointCursorController();
8519        stopSelectionActionMode();
8520    }
8521
8522    /**
8523     * Get the character offset closest to the specified absolute position. A typical use case is to
8524     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8525     *
8526     * @param x The horizontal absolute position of a point on screen
8527     * @param y The vertical absolute position of a point on screen
8528     * @return the character offset for the character whose position is closest to the specified
8529     *  position. Returns -1 if there is no layout.
8530     */
8531    public int getOffsetForPosition(float x, float y) {
8532        if (getLayout() == null) return -1;
8533        final int line = getLineAtCoordinate(y);
8534        final int offset = getOffsetAtCoordinate(line, x);
8535        return offset;
8536    }
8537
8538    private float convertToLocalHorizontalCoordinate(float x) {
8539        x -= getTotalPaddingLeft();
8540        // Clamp the position to inside of the view.
8541        x = Math.max(0.0f, x);
8542        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8543        x += getScrollX();
8544        return x;
8545    }
8546
8547    private int getLineAtCoordinate(float y) {
8548        y -= getTotalPaddingTop();
8549        // Clamp the position to inside of the view.
8550        y = Math.max(0.0f, y);
8551        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8552        y += getScrollY();
8553        return getLayout().getLineForVertical((int) y);
8554    }
8555
8556    private int getOffsetAtCoordinate(int line, float x) {
8557        x = convertToLocalHorizontalCoordinate(x);
8558        return getLayout().getOffsetForHorizontal(line, x);
8559    }
8560
8561    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
8562     * in the view. Returns false when the position is in the empty space of left/right of text.
8563     */
8564    private boolean isPositionOnText(float x, float y) {
8565        if (getLayout() == null) return false;
8566
8567        final int line = getLineAtCoordinate(y);
8568        x = convertToLocalHorizontalCoordinate(x);
8569
8570        if (x < getLayout().getLineLeft(line)) return false;
8571        if (x > getLayout().getLineRight(line)) return false;
8572        return true;
8573    }
8574
8575    @Override
8576    public boolean onDragEvent(DragEvent event) {
8577        switch (event.getAction()) {
8578            case DragEvent.ACTION_DRAG_STARTED:
8579                return mEditor != null && hasInsertionController();
8580
8581            case DragEvent.ACTION_DRAG_ENTERED:
8582                TextView.this.requestFocus();
8583                return true;
8584
8585            case DragEvent.ACTION_DRAG_LOCATION:
8586                final int offset = getOffsetForPosition(event.getX(), event.getY());
8587                Selection.setSelection((Spannable)mText, offset);
8588                return true;
8589
8590            case DragEvent.ACTION_DROP:
8591                onDrop(event);
8592                return true;
8593
8594            case DragEvent.ACTION_DRAG_ENDED:
8595            case DragEvent.ACTION_DRAG_EXITED:
8596            default:
8597                return true;
8598        }
8599    }
8600
8601    private void onDrop(DragEvent event) {
8602        StringBuilder content = new StringBuilder("");
8603        ClipData clipData = event.getClipData();
8604        final int itemCount = clipData.getItemCount();
8605        for (int i=0; i < itemCount; i++) {
8606            Item item = clipData.getItemAt(i);
8607            content.append(item.coerceToText(TextView.this.mContext));
8608        }
8609
8610        final int offset = getOffsetForPosition(event.getX(), event.getY());
8611
8612        Object localState = event.getLocalState();
8613        DragLocalState dragLocalState = null;
8614        if (localState instanceof DragLocalState) {
8615            dragLocalState = (DragLocalState) localState;
8616        }
8617        boolean dragDropIntoItself = dragLocalState != null &&
8618                dragLocalState.sourceTextView == this;
8619
8620        if (dragDropIntoItself) {
8621            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
8622                // A drop inside the original selection discards the drop.
8623                return;
8624            }
8625        }
8626
8627        final int originalLength = mText.length();
8628        long minMax = prepareSpacesAroundPaste(offset, offset, content);
8629        int min = extractRangeStartFromLong(minMax);
8630        int max = extractRangeEndFromLong(minMax);
8631
8632        Selection.setSelection((Spannable) mText, max);
8633        replaceText_internal(min, max, content);
8634
8635        if (dragDropIntoItself) {
8636            int dragSourceStart = dragLocalState.start;
8637            int dragSourceEnd = dragLocalState.end;
8638            if (max <= dragSourceStart) {
8639                // Inserting text before selection has shifted positions
8640                final int shift = mText.length() - originalLength;
8641                dragSourceStart += shift;
8642                dragSourceEnd += shift;
8643            }
8644
8645            // Delete original selection
8646            deleteText_internal(dragSourceStart, dragSourceEnd);
8647
8648            // Make sure we do not leave two adjacent spaces.
8649            if ((dragSourceStart == 0 ||
8650                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
8651                    (dragSourceStart == mText.length() ||
8652                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
8653                final int pos = dragSourceStart == mText.length() ?
8654                        dragSourceStart - 1 : dragSourceStart;
8655                deleteText_internal(pos, pos + 1);
8656            }
8657        }
8658    }
8659
8660    /**
8661     * @return True if this view supports insertion handles.
8662     */
8663    boolean hasInsertionController() {
8664        return getEditor().mInsertionControllerEnabled;
8665    }
8666
8667    /**
8668     * @return True if this view supports selection handles.
8669     */
8670    boolean hasSelectionController() {
8671        return getEditor().mSelectionControllerEnabled;
8672    }
8673
8674    InsertionPointCursorController getInsertionController() {
8675        if (!getEditor().mInsertionControllerEnabled) {
8676            return null;
8677        }
8678
8679        if (getEditor().mInsertionPointCursorController == null) {
8680            getEditor().mInsertionPointCursorController = new InsertionPointCursorController();
8681
8682            final ViewTreeObserver observer = getViewTreeObserver();
8683            observer.addOnTouchModeChangeListener(getEditor().mInsertionPointCursorController);
8684        }
8685
8686        return getEditor().mInsertionPointCursorController;
8687    }
8688
8689    SelectionModifierCursorController getSelectionController() {
8690        if (!getEditor().mSelectionControllerEnabled) {
8691            return null;
8692        }
8693
8694        if (getEditor().mSelectionModifierCursorController == null) {
8695            getEditor().mSelectionModifierCursorController = new SelectionModifierCursorController();
8696
8697            final ViewTreeObserver observer = getViewTreeObserver();
8698            observer.addOnTouchModeChangeListener(getEditor().mSelectionModifierCursorController);
8699        }
8700
8701        return getEditor().mSelectionModifierCursorController;
8702    }
8703
8704    boolean isInBatchEditMode() {
8705        if (mEditor == null) return false;
8706        final InputMethodState ims = getEditor().mInputMethodState;
8707        if (ims != null) {
8708            return ims.mBatchEditNesting > 0;
8709        }
8710        return getEditor().mInBatchEditControllers;
8711    }
8712
8713    @Override
8714    public void onResolveTextDirection() {
8715        if (hasPasswordTransformationMethod()) {
8716            mTextDir = TextDirectionHeuristics.LOCALE;
8717            return;
8718        }
8719
8720        // Always need to resolve layout direction first
8721        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
8722
8723        // Now, we can select the heuristic
8724        int textDir = getResolvedTextDirection();
8725        switch (textDir) {
8726            default:
8727            case TEXT_DIRECTION_FIRST_STRONG:
8728                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
8729                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
8730                break;
8731            case TEXT_DIRECTION_ANY_RTL:
8732                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
8733                break;
8734            case TEXT_DIRECTION_LTR:
8735                mTextDir = TextDirectionHeuristics.LTR;
8736                break;
8737            case TEXT_DIRECTION_RTL:
8738                mTextDir = TextDirectionHeuristics.RTL;
8739                break;
8740            case TEXT_DIRECTION_LOCALE:
8741                mTextDir = TextDirectionHeuristics.LOCALE;
8742                break;
8743        }
8744    }
8745
8746    /**
8747     * Subclasses will need to override this method to implement their own way of resolving
8748     * drawables depending on the layout direction.
8749     *
8750     * A call to the super method will be required from the subclasses implementation.
8751     */
8752    protected void resolveDrawables() {
8753        // No need to resolve twice
8754        if (mResolvedDrawables) {
8755            return;
8756        }
8757        // No drawable to resolve
8758        if (mDrawables == null) {
8759            return;
8760        }
8761        // No relative drawable to resolve
8762        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
8763            mResolvedDrawables = true;
8764            return;
8765        }
8766
8767        Drawables dr = mDrawables;
8768        switch(getResolvedLayoutDirection()) {
8769            case LAYOUT_DIRECTION_RTL:
8770                if (dr.mDrawableStart != null) {
8771                    dr.mDrawableRight = dr.mDrawableStart;
8772
8773                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
8774                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
8775                }
8776                if (dr.mDrawableEnd != null) {
8777                    dr.mDrawableLeft = dr.mDrawableEnd;
8778
8779                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
8780                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
8781                }
8782                break;
8783
8784            case LAYOUT_DIRECTION_LTR:
8785            default:
8786                if (dr.mDrawableStart != null) {
8787                    dr.mDrawableLeft = dr.mDrawableStart;
8788
8789                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
8790                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
8791                }
8792                if (dr.mDrawableEnd != null) {
8793                    dr.mDrawableRight = dr.mDrawableEnd;
8794
8795                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
8796                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
8797                }
8798                break;
8799        }
8800        mResolvedDrawables = true;
8801    }
8802
8803    protected void resetResolvedDrawables() {
8804        mResolvedDrawables = false;
8805    }
8806
8807    /**
8808     * @hide
8809     */
8810    protected void viewClicked(InputMethodManager imm) {
8811        if (imm != null) {
8812            imm.viewClicked(this);
8813        }
8814    }
8815
8816    /**
8817     * Deletes the range of text [start, end[.
8818     * @hide
8819     */
8820    protected void deleteText_internal(int start, int end) {
8821        ((Editable) mText).delete(start, end);
8822    }
8823
8824    /**
8825     * Replaces the range of text [start, end[ by replacement text
8826     * @hide
8827     */
8828    protected void replaceText_internal(int start, int end, CharSequence text) {
8829        ((Editable) mText).replace(start, end, text);
8830    }
8831
8832    /**
8833     * Sets a span on the specified range of text
8834     * @hide
8835     */
8836    protected void setSpan_internal(Object span, int start, int end, int flags) {
8837        ((Editable) mText).setSpan(span, start, end, flags);
8838    }
8839
8840    /**
8841     * Moves the cursor to the specified offset position in text
8842     * @hide
8843     */
8844    protected void setCursorPosition_internal(int start, int end) {
8845        Selection.setSelection(((Editable) mText), start, end);
8846    }
8847
8848    /**
8849     * An Editor should be created as soon as any of the editable-specific fields (grouped
8850     * inside the Editor object) is assigned to a non-default value.
8851     * This method will create the Editor if needed.
8852     *
8853     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8854     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8855     * Editor for backward compatibility, as soon as one of these fields is assigned.
8856     *
8857     * Also note that for performance reasons, the mEditor is created when needed, but not
8858     * reset when no more edit-specific fields are needed.
8859     */
8860    private void createEditorIfNeeded(String reason) {
8861        if (mEditor == null) {
8862            if (!(this instanceof EditText)) {
8863                Log.e(LOG_TAG + " EDITOR", "Creating Editor on TextView. " + reason);
8864            }
8865            mEditor = new Editor();
8866        } else {
8867            if (!(this instanceof EditText)) {
8868                Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason);
8869            }
8870        }
8871    }
8872
8873    private Editor getEditor() {
8874        if (mEditor == null) {
8875            //createEditorIfNeeded("Problem: mEditor is not initialized!");
8876            Log.e(LOG_TAG, "mEditor not initialized. Please send a bug report to debunne@");
8877        }
8878        return mEditor;
8879    }
8880
8881    /**
8882     * User interface state that is stored by TextView for implementing
8883     * {@link View#onSaveInstanceState}.
8884     */
8885    public static class SavedState extends BaseSavedState {
8886        int selStart;
8887        int selEnd;
8888        CharSequence text;
8889        boolean frozenWithFocus;
8890        CharSequence error;
8891
8892        SavedState(Parcelable superState) {
8893            super(superState);
8894        }
8895
8896        @Override
8897        public void writeToParcel(Parcel out, int flags) {
8898            super.writeToParcel(out, flags);
8899            out.writeInt(selStart);
8900            out.writeInt(selEnd);
8901            out.writeInt(frozenWithFocus ? 1 : 0);
8902            TextUtils.writeToParcel(text, out, flags);
8903
8904            if (error == null) {
8905                out.writeInt(0);
8906            } else {
8907                out.writeInt(1);
8908                TextUtils.writeToParcel(error, out, flags);
8909            }
8910        }
8911
8912        @Override
8913        public String toString() {
8914            String str = "TextView.SavedState{"
8915                    + Integer.toHexString(System.identityHashCode(this))
8916                    + " start=" + selStart + " end=" + selEnd;
8917            if (text != null) {
8918                str += " text=" + text;
8919            }
8920            return str + "}";
8921        }
8922
8923        @SuppressWarnings("hiding")
8924        public static final Parcelable.Creator<SavedState> CREATOR
8925                = new Parcelable.Creator<SavedState>() {
8926            public SavedState createFromParcel(Parcel in) {
8927                return new SavedState(in);
8928            }
8929
8930            public SavedState[] newArray(int size) {
8931                return new SavedState[size];
8932            }
8933        };
8934
8935        private SavedState(Parcel in) {
8936            super(in);
8937            selStart = in.readInt();
8938            selEnd = in.readInt();
8939            frozenWithFocus = (in.readInt() != 0);
8940            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8941
8942            if (in.readInt() != 0) {
8943                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8944            }
8945        }
8946    }
8947
8948    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8949        private char[] mChars;
8950        private int mStart, mLength;
8951
8952        public CharWrapper(char[] chars, int start, int len) {
8953            mChars = chars;
8954            mStart = start;
8955            mLength = len;
8956        }
8957
8958        /* package */ void set(char[] chars, int start, int len) {
8959            mChars = chars;
8960            mStart = start;
8961            mLength = len;
8962        }
8963
8964        public int length() {
8965            return mLength;
8966        }
8967
8968        public char charAt(int off) {
8969            return mChars[off + mStart];
8970        }
8971
8972        @Override
8973        public String toString() {
8974            return new String(mChars, mStart, mLength);
8975        }
8976
8977        public CharSequence subSequence(int start, int end) {
8978            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8979                throw new IndexOutOfBoundsException(start + ", " + end);
8980            }
8981
8982            return new String(mChars, start + mStart, end - start);
8983        }
8984
8985        public void getChars(int start, int end, char[] buf, int off) {
8986            if (start < 0 || end < 0 || start > mLength || end > mLength) {
8987                throw new IndexOutOfBoundsException(start + ", " + end);
8988            }
8989
8990            System.arraycopy(mChars, start + mStart, buf, off, end - start);
8991        }
8992
8993        public void drawText(Canvas c, int start, int end,
8994                             float x, float y, Paint p) {
8995            c.drawText(mChars, start + mStart, end - start, x, y, p);
8996        }
8997
8998        public void drawTextRun(Canvas c, int start, int end,
8999                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
9000            int count = end - start;
9001            int contextCount = contextEnd - contextStart;
9002            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9003                    contextCount, x, y, flags, p);
9004        }
9005
9006        public float measureText(int start, int end, Paint p) {
9007            return p.measureText(mChars, start + mStart, end - start);
9008        }
9009
9010        public int getTextWidths(int start, int end, float[] widths, Paint p) {
9011            return p.getTextWidths(mChars, start + mStart, end - start, widths);
9012        }
9013
9014        public float getTextRunAdvances(int start, int end, int contextStart,
9015                int contextEnd, int flags, float[] advances, int advancesIndex,
9016                Paint p) {
9017            int count = end - start;
9018            int contextCount = contextEnd - contextStart;
9019            return p.getTextRunAdvances(mChars, start + mStart, count,
9020                    contextStart + mStart, contextCount, flags, advances,
9021                    advancesIndex);
9022        }
9023
9024        public float getTextRunAdvances(int start, int end, int contextStart,
9025                int contextEnd, int flags, float[] advances, int advancesIndex,
9026                Paint p, int reserved) {
9027            int count = end - start;
9028            int contextCount = contextEnd - contextStart;
9029            return p.getTextRunAdvances(mChars, start + mStart, count,
9030                    contextStart + mStart, contextCount, flags, advances,
9031                    advancesIndex, reserved);
9032        }
9033
9034        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
9035                int offset, int cursorOpt, Paint p) {
9036            int contextCount = contextEnd - contextStart;
9037            return p.getTextRunCursor(mChars, contextStart + mStart,
9038                    contextCount, flags, offset + mStart, cursorOpt);
9039        }
9040    }
9041
9042    private static class ErrorPopup extends PopupWindow {
9043        private boolean mAbove = false;
9044        private final TextView mView;
9045        private int mPopupInlineErrorBackgroundId = 0;
9046        private int mPopupInlineErrorAboveBackgroundId = 0;
9047
9048        ErrorPopup(TextView v, int width, int height) {
9049            super(v, width, height);
9050            mView = v;
9051            // Make sure the TextView has a background set as it will be used the first time it is
9052            // shown and positionned. Initialized with below background, which should have
9053            // dimensions identical to the above version for this to work (and is more likely).
9054            mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
9055                    com.android.internal.R.styleable.Theme_errorMessageBackground);
9056            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
9057        }
9058
9059        void fixDirection(boolean above) {
9060            mAbove = above;
9061
9062            if (above) {
9063                mPopupInlineErrorAboveBackgroundId =
9064                    getResourceId(mPopupInlineErrorAboveBackgroundId,
9065                            com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
9066            } else {
9067                mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
9068                        com.android.internal.R.styleable.Theme_errorMessageBackground);
9069            }
9070
9071            mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
9072                mPopupInlineErrorBackgroundId);
9073        }
9074
9075        private int getResourceId(int currentId, int index) {
9076            if (currentId == 0) {
9077                TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
9078                        R.styleable.Theme);
9079                currentId = styledAttributes.getResourceId(index, 0);
9080                styledAttributes.recycle();
9081            }
9082            return currentId;
9083        }
9084
9085        @Override
9086        public void update(int x, int y, int w, int h, boolean force) {
9087            super.update(x, y, w, h, force);
9088
9089            boolean above = isAboveAnchor();
9090            if (above != mAbove) {
9091                fixDirection(above);
9092            }
9093        }
9094    }
9095
9096    private class CorrectionHighlighter {
9097        private final Path mPath = new Path();
9098        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
9099        private int mStart, mEnd;
9100        private long mFadingStartTime;
9101        private final static int FADE_OUT_DURATION = 400;
9102
9103        public CorrectionHighlighter() {
9104            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
9105            mPaint.setStyle(Paint.Style.FILL);
9106        }
9107
9108        public void highlight(CorrectionInfo info) {
9109            mStart = info.getOffset();
9110            mEnd = mStart + info.getNewText().length();
9111            mFadingStartTime = SystemClock.uptimeMillis();
9112
9113            if (mStart < 0 || mEnd < 0) {
9114                stopAnimation();
9115            }
9116        }
9117
9118        public void draw(Canvas canvas, int cursorOffsetVertical) {
9119            if (updatePath() && updatePaint()) {
9120                if (cursorOffsetVertical != 0) {
9121                    canvas.translate(0, cursorOffsetVertical);
9122                }
9123
9124                canvas.drawPath(mPath, mPaint);
9125
9126                if (cursorOffsetVertical != 0) {
9127                    canvas.translate(0, -cursorOffsetVertical);
9128                }
9129                invalidate(true); // TODO invalidate cursor region only
9130            } else {
9131                stopAnimation();
9132                invalidate(false); // TODO invalidate cursor region only
9133            }
9134        }
9135
9136        private boolean updatePaint() {
9137            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
9138            if (duration > FADE_OUT_DURATION) return false;
9139
9140            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
9141            final int highlightColorAlpha = Color.alpha(mHighlightColor);
9142            final int color = (mHighlightColor & 0x00FFFFFF) +
9143                    ((int) (highlightColorAlpha * coef) << 24);
9144            mPaint.setColor(color);
9145            return true;
9146        }
9147
9148        private boolean updatePath() {
9149            final Layout layout = TextView.this.mLayout;
9150            if (layout == null) return false;
9151
9152            // Update in case text is edited while the animation is run
9153            final int length = mText.length();
9154            int start = Math.min(length, mStart);
9155            int end = Math.min(length, mEnd);
9156
9157            mPath.reset();
9158            TextView.this.mLayout.getSelectionPath(start, end, mPath);
9159            return true;
9160        }
9161
9162        private void invalidate(boolean delayed) {
9163            if (TextView.this.mLayout == null) return;
9164
9165            synchronized (TEMP_RECTF) {
9166                mPath.computeBounds(TEMP_RECTF, false);
9167
9168                int left = getCompoundPaddingLeft();
9169                int top = getExtendedPaddingTop() + getVerticalOffset(true);
9170
9171                if (delayed) {
9172                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
9173                            left + (int) TEMP_RECTF.left, top + (int) TEMP_RECTF.top,
9174                            left + (int) TEMP_RECTF.right, top + (int) TEMP_RECTF.bottom);
9175                } else {
9176                    TextView.this.postInvalidate((int) TEMP_RECTF.left, (int) TEMP_RECTF.top,
9177                            (int) TEMP_RECTF.right, (int) TEMP_RECTF.bottom);
9178                }
9179            }
9180        }
9181
9182        private void stopAnimation() {
9183            TextView.this.getEditor().mCorrectionHighlighter = null;
9184        }
9185    }
9186
9187    private static final class Marquee extends Handler {
9188        // TODO: Add an option to configure this
9189        private static final float MARQUEE_DELTA_MAX = 0.07f;
9190        private static final int MARQUEE_DELAY = 1200;
9191        private static final int MARQUEE_RESTART_DELAY = 1200;
9192        private static final int MARQUEE_RESOLUTION = 1000 / 30;
9193        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
9194
9195        private static final byte MARQUEE_STOPPED = 0x0;
9196        private static final byte MARQUEE_STARTING = 0x1;
9197        private static final byte MARQUEE_RUNNING = 0x2;
9198
9199        private static final int MESSAGE_START = 0x1;
9200        private static final int MESSAGE_TICK = 0x2;
9201        private static final int MESSAGE_RESTART = 0x3;
9202
9203        private final WeakReference<TextView> mView;
9204
9205        private byte mStatus = MARQUEE_STOPPED;
9206        private final float mScrollUnit;
9207        private float mMaxScroll;
9208        float mMaxFadeScroll;
9209        private float mGhostStart;
9210        private float mGhostOffset;
9211        private float mFadeStop;
9212        private int mRepeatLimit;
9213
9214        float mScroll;
9215
9216        Marquee(TextView v) {
9217            final float density = v.getContext().getResources().getDisplayMetrics().density;
9218            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
9219            mView = new WeakReference<TextView>(v);
9220        }
9221
9222        @Override
9223        public void handleMessage(Message msg) {
9224            switch (msg.what) {
9225                case MESSAGE_START:
9226                    mStatus = MARQUEE_RUNNING;
9227                    tick();
9228                    break;
9229                case MESSAGE_TICK:
9230                    tick();
9231                    break;
9232                case MESSAGE_RESTART:
9233                    if (mStatus == MARQUEE_RUNNING) {
9234                        if (mRepeatLimit >= 0) {
9235                            mRepeatLimit--;
9236                        }
9237                        start(mRepeatLimit);
9238                    }
9239                    break;
9240            }
9241        }
9242
9243        void tick() {
9244            if (mStatus != MARQUEE_RUNNING) {
9245                return;
9246            }
9247
9248            removeMessages(MESSAGE_TICK);
9249
9250            final TextView textView = mView.get();
9251            if (textView != null && (textView.isFocused() || textView.isSelected())) {
9252                mScroll += mScrollUnit;
9253                if (mScroll > mMaxScroll) {
9254                    mScroll = mMaxScroll;
9255                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
9256                } else {
9257                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
9258                }
9259                textView.invalidate();
9260            }
9261        }
9262
9263        void stop() {
9264            mStatus = MARQUEE_STOPPED;
9265            removeMessages(MESSAGE_START);
9266            removeMessages(MESSAGE_RESTART);
9267            removeMessages(MESSAGE_TICK);
9268            resetScroll();
9269        }
9270
9271        private void resetScroll() {
9272            mScroll = 0.0f;
9273            final TextView textView = mView.get();
9274            if (textView != null) textView.invalidate();
9275        }
9276
9277        void start(int repeatLimit) {
9278            if (repeatLimit == 0) {
9279                stop();
9280                return;
9281            }
9282            mRepeatLimit = repeatLimit;
9283            final TextView textView = mView.get();
9284            if (textView != null && textView.mLayout != null) {
9285                mStatus = MARQUEE_STARTING;
9286                mScroll = 0.0f;
9287                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9288                        textView.getCompoundPaddingRight();
9289                final float lineWidth = textView.mLayout.getLineWidth(0);
9290                final float gap = textWidth / 3.0f;
9291                mGhostStart = lineWidth - textWidth + gap;
9292                mMaxScroll = mGhostStart + textWidth;
9293                mGhostOffset = lineWidth + gap;
9294                mFadeStop = lineWidth + textWidth / 6.0f;
9295                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9296
9297                textView.invalidate();
9298                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9299            }
9300        }
9301
9302        float getGhostOffset() {
9303            return mGhostOffset;
9304        }
9305
9306        boolean shouldDrawLeftFade() {
9307            return mScroll <= mFadeStop;
9308        }
9309
9310        boolean shouldDrawGhost() {
9311            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9312        }
9313
9314        boolean isRunning() {
9315            return mStatus == MARQUEE_RUNNING;
9316        }
9317
9318        boolean isStopped() {
9319            return mStatus == MARQUEE_STOPPED;
9320        }
9321    }
9322
9323    /**
9324     * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
9325     * pop-up should be displayed.
9326     */
9327    private class EasyEditSpanController {
9328
9329        private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
9330
9331        private EasyEditPopupWindow mPopupWindow;
9332
9333        private EasyEditSpan mEasyEditSpan;
9334
9335        private Runnable mHidePopup;
9336
9337        private void hide() {
9338            if (mPopupWindow != null) {
9339                mPopupWindow.hide();
9340                TextView.this.removeCallbacks(mHidePopup);
9341            }
9342            removeSpans(mText);
9343            mEasyEditSpan = null;
9344        }
9345
9346        /**
9347         * Monitors the changes in the text.
9348         *
9349         * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
9350         * as the notifications are not sent when a spannable (with spans) is inserted.
9351         */
9352        public void onTextChange(CharSequence buffer) {
9353            adjustSpans(mText);
9354
9355            if (getWindowVisibility() != View.VISIBLE) {
9356                // The window is not visible yet, ignore the text change.
9357                return;
9358            }
9359
9360            if (mLayout == null) {
9361                // The view has not been layout yet, ignore the text change
9362                return;
9363            }
9364
9365            InputMethodManager imm = InputMethodManager.peekInstance();
9366            if (!(TextView.this instanceof ExtractEditText)
9367                    && imm != null && imm.isFullscreenMode()) {
9368                // The input is in extract mode. We do not have to handle the easy edit in the
9369                // original TextView, as the ExtractEditText will do
9370                return;
9371            }
9372
9373            // Remove the current easy edit span, as the text changed, and remove the pop-up
9374            // (if any)
9375            if (mEasyEditSpan != null) {
9376                if (mText instanceof Spannable) {
9377                    ((Spannable) mText).removeSpan(mEasyEditSpan);
9378                }
9379                mEasyEditSpan = null;
9380            }
9381            if (mPopupWindow != null && mPopupWindow.isShowing()) {
9382                mPopupWindow.hide();
9383            }
9384
9385            // Display the new easy edit span (if any).
9386            if (buffer instanceof Spanned) {
9387                mEasyEditSpan = getSpan((Spanned) buffer);
9388                if (mEasyEditSpan != null) {
9389                    if (mPopupWindow == null) {
9390                        mPopupWindow = new EasyEditPopupWindow();
9391                        mHidePopup = new Runnable() {
9392                            @Override
9393                            public void run() {
9394                                hide();
9395                            }
9396                        };
9397                    }
9398                    mPopupWindow.show(mEasyEditSpan);
9399                    TextView.this.removeCallbacks(mHidePopup);
9400                    TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
9401                }
9402            }
9403        }
9404
9405        /**
9406         * Adjusts the spans by removing all of them except the last one.
9407         */
9408        private void adjustSpans(CharSequence buffer) {
9409            // This method enforces that only one easy edit span is attached to the text.
9410            // A better way to enforce this would be to listen for onSpanAdded, but this method
9411            // cannot be used in this scenario as no notification is triggered when a text with
9412            // spans is inserted into a text.
9413            if (buffer instanceof Spannable) {
9414                Spannable spannable = (Spannable) buffer;
9415                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
9416                        EasyEditSpan.class);
9417                for (int i = 0; i < spans.length - 1; i++) {
9418                    spannable.removeSpan(spans[i]);
9419                }
9420            }
9421        }
9422
9423        /**
9424         * Removes all the {@link EasyEditSpan} currently attached.
9425         */
9426        private void removeSpans(CharSequence buffer) {
9427            if (buffer instanceof Spannable) {
9428                Spannable spannable = (Spannable) buffer;
9429                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
9430                        EasyEditSpan.class);
9431                for (int i = 0; i < spans.length; i++) {
9432                    spannable.removeSpan(spans[i]);
9433                }
9434            }
9435        }
9436
9437        private EasyEditSpan getSpan(Spanned spanned) {
9438            EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
9439                    EasyEditSpan.class);
9440            if (easyEditSpans.length == 0) {
9441                return null;
9442            } else {
9443                return easyEditSpans[0];
9444            }
9445        }
9446    }
9447
9448    /**
9449     * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
9450     * by {@link EasyEditSpanController}.
9451     */
9452    private class EasyEditPopupWindow extends PinnedPopupWindow
9453            implements OnClickListener {
9454        private static final int POPUP_TEXT_LAYOUT =
9455                com.android.internal.R.layout.text_edit_action_popup_text;
9456        private TextView mDeleteTextView;
9457        private EasyEditSpan mEasyEditSpan;
9458
9459        @Override
9460        protected void createPopupWindow() {
9461            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
9462                    com.android.internal.R.attr.textSelectHandleWindowStyle);
9463            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
9464            mPopupWindow.setClippingEnabled(true);
9465        }
9466
9467        @Override
9468        protected void initContentView() {
9469            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
9470            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
9471            mContentView = linearLayout;
9472            mContentView.setBackgroundResource(
9473                    com.android.internal.R.drawable.text_edit_side_paste_window);
9474
9475            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
9476                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9477
9478            LayoutParams wrapContent = new LayoutParams(
9479                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
9480
9481            mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
9482            mDeleteTextView.setLayoutParams(wrapContent);
9483            mDeleteTextView.setText(com.android.internal.R.string.delete);
9484            mDeleteTextView.setOnClickListener(this);
9485            mContentView.addView(mDeleteTextView);
9486        }
9487
9488        public void show(EasyEditSpan easyEditSpan) {
9489            mEasyEditSpan = easyEditSpan;
9490            super.show();
9491        }
9492
9493        @Override
9494        public void onClick(View view) {
9495            if (view == mDeleteTextView) {
9496                Editable editable = (Editable) mText;
9497                int start = editable.getSpanStart(mEasyEditSpan);
9498                int end = editable.getSpanEnd(mEasyEditSpan);
9499                if (start >= 0 && end >= 0) {
9500                    deleteText_internal(start, end);
9501                }
9502            }
9503        }
9504
9505        @Override
9506        protected int getTextOffset() {
9507            // Place the pop-up at the end of the span
9508            Editable editable = (Editable) mText;
9509            return editable.getSpanEnd(mEasyEditSpan);
9510        }
9511
9512        @Override
9513        protected int getVerticalLocalPosition(int line) {
9514            return mLayout.getLineBottom(line);
9515        }
9516
9517        @Override
9518        protected int clipVertically(int positionY) {
9519            // As we display the pop-up below the span, no vertical clipping is required.
9520            return positionY;
9521        }
9522    }
9523
9524    private class ChangeWatcher implements TextWatcher, SpanWatcher {
9525
9526        private CharSequence mBeforeText;
9527
9528        private EasyEditSpanController mEasyEditSpanController;
9529
9530        private ChangeWatcher() {
9531            mEasyEditSpanController = new EasyEditSpanController();
9532        }
9533
9534        public void beforeTextChanged(CharSequence buffer, int start,
9535                                      int before, int after) {
9536            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9537                    + " before=" + before + " after=" + after + ": " + buffer);
9538
9539            if (AccessibilityManager.getInstance(mContext).isEnabled()
9540                    && !isPasswordInputType(getInputType())
9541                    && !hasPasswordTransformationMethod()) {
9542                mBeforeText = buffer.toString();
9543            }
9544
9545            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9546        }
9547
9548        public void onTextChanged(CharSequence buffer, int start,
9549                                  int before, int after) {
9550            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9551                    + " before=" + before + " after=" + after + ": " + buffer);
9552            TextView.this.handleTextChanged(buffer, start, before, after);
9553
9554            mEasyEditSpanController.onTextChange(buffer);
9555
9556            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9557                    (isFocused() || isSelected() && isShown())) {
9558                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9559                mBeforeText = null;
9560            }
9561        }
9562
9563        public void afterTextChanged(Editable buffer) {
9564            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9565            TextView.this.sendAfterTextChanged(buffer);
9566
9567            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9568                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9569            }
9570        }
9571
9572        public void onSpanChanged(Spannable buf,
9573                                  Object what, int s, int e, int st, int en) {
9574            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9575                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9576            TextView.this.spanChange(buf, what, s, st, e, en);
9577        }
9578
9579        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9580            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9581                    + " what=" + what + ": " + buf);
9582            TextView.this.spanChange(buf, what, -1, s, -1, e);
9583        }
9584
9585        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9586            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9587                    + " what=" + what + ": " + buf);
9588            TextView.this.spanChange(buf, what, s, -1, e, -1);
9589        }
9590
9591        private void hideControllers() {
9592            mEasyEditSpanController.hide();
9593        }
9594    }
9595
9596    private static class Blink extends Handler implements Runnable {
9597        private final WeakReference<TextView> mView;
9598        private boolean mCancelled;
9599
9600        public Blink(TextView v) {
9601            mView = new WeakReference<TextView>(v);
9602        }
9603
9604        public void run() {
9605            if (mCancelled) {
9606                return;
9607            }
9608
9609            removeCallbacks(Blink.this);
9610
9611            TextView tv = mView.get();
9612
9613            if (tv != null && tv.shouldBlink()) {
9614                if (tv.mLayout != null) {
9615                    tv.invalidateCursorPath();
9616                }
9617
9618                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
9619            }
9620        }
9621
9622        void cancel() {
9623            if (!mCancelled) {
9624                removeCallbacks(Blink.this);
9625                mCancelled = true;
9626            }
9627        }
9628
9629        void uncancel() {
9630            mCancelled = false;
9631        }
9632    }
9633
9634    private static class DragLocalState {
9635        public TextView sourceTextView;
9636        public int start, end;
9637
9638        public DragLocalState(TextView sourceTextView, int start, int end) {
9639            this.sourceTextView = sourceTextView;
9640            this.start = start;
9641            this.end = end;
9642        }
9643    }
9644
9645    private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
9646        // 3 handles
9647        // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
9648        private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
9649        private TextViewPositionListener[] mPositionListeners =
9650                new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
9651        private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
9652        private boolean mPositionHasChanged = true;
9653        // Absolute position of the TextView with respect to its parent window
9654        private int mPositionX, mPositionY;
9655        private int mNumberOfListeners;
9656        private boolean mScrollHasChanged;
9657        final int[] mTempCoords = new int[2];
9658
9659        public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
9660            if (mNumberOfListeners == 0) {
9661                updatePosition();
9662                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9663                vto.addOnPreDrawListener(this);
9664            }
9665
9666            int emptySlotIndex = -1;
9667            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9668                TextViewPositionListener listener = mPositionListeners[i];
9669                if (listener == positionListener) {
9670                    return;
9671                } else if (emptySlotIndex < 0 && listener == null) {
9672                    emptySlotIndex = i;
9673                }
9674            }
9675
9676            mPositionListeners[emptySlotIndex] = positionListener;
9677            mCanMove[emptySlotIndex] = canMove;
9678            mNumberOfListeners++;
9679        }
9680
9681        public void removeSubscriber(TextViewPositionListener positionListener) {
9682            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9683                if (mPositionListeners[i] == positionListener) {
9684                    mPositionListeners[i] = null;
9685                    mNumberOfListeners--;
9686                    break;
9687                }
9688            }
9689
9690            if (mNumberOfListeners == 0) {
9691                ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9692                vto.removeOnPreDrawListener(this);
9693            }
9694        }
9695
9696        public int getPositionX() {
9697            return mPositionX;
9698        }
9699
9700        public int getPositionY() {
9701            return mPositionY;
9702        }
9703
9704        @Override
9705        public boolean onPreDraw() {
9706            updatePosition();
9707
9708            for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
9709                if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
9710                    TextViewPositionListener positionListener = mPositionListeners[i];
9711                    if (positionListener != null) {
9712                        positionListener.updatePosition(mPositionX, mPositionY,
9713                                mPositionHasChanged, mScrollHasChanged);
9714                    }
9715                }
9716            }
9717
9718            mScrollHasChanged = false;
9719            return true;
9720        }
9721
9722        private void updatePosition() {
9723            TextView.this.getLocationInWindow(mTempCoords);
9724
9725            mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
9726
9727            mPositionX = mTempCoords[0];
9728            mPositionY = mTempCoords[1];
9729        }
9730
9731        public void onScrollChanged() {
9732            mScrollHasChanged = true;
9733        }
9734    }
9735
9736    private abstract class PinnedPopupWindow implements TextViewPositionListener {
9737        protected PopupWindow mPopupWindow;
9738        protected ViewGroup mContentView;
9739        int mPositionX, mPositionY;
9740
9741        protected abstract void createPopupWindow();
9742        protected abstract void initContentView();
9743        protected abstract int getTextOffset();
9744        protected abstract int getVerticalLocalPosition(int line);
9745        protected abstract int clipVertically(int positionY);
9746
9747        public PinnedPopupWindow() {
9748            createPopupWindow();
9749
9750            mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
9751            mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
9752            mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
9753
9754            initContentView();
9755
9756            LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
9757                    ViewGroup.LayoutParams.WRAP_CONTENT);
9758            mContentView.setLayoutParams(wrapContent);
9759
9760            mPopupWindow.setContentView(mContentView);
9761        }
9762
9763        public void show() {
9764            TextView.this.getPositionListener().addSubscriber(this, false /* offset is fixed */);
9765
9766            computeLocalPosition();
9767
9768            final PositionListener positionListener = TextView.this.getPositionListener();
9769            updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
9770        }
9771
9772        protected void measureContent() {
9773            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9774            mContentView.measure(
9775                    View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
9776                            View.MeasureSpec.AT_MOST),
9777                    View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
9778                            View.MeasureSpec.AT_MOST));
9779        }
9780
9781        /* The popup window will be horizontally centered on the getTextOffset() and vertically
9782         * positioned according to viewportToContentHorizontalOffset.
9783         *
9784         * This method assumes that mContentView has properly been measured from its content. */
9785        private void computeLocalPosition() {
9786            measureContent();
9787            final int width = mContentView.getMeasuredWidth();
9788            final int offset = getTextOffset();
9789            mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
9790            mPositionX += viewportToContentHorizontalOffset();
9791
9792            final int line = mLayout.getLineForOffset(offset);
9793            mPositionY = getVerticalLocalPosition(line);
9794            mPositionY += viewportToContentVerticalOffset();
9795        }
9796
9797        private void updatePosition(int parentPositionX, int parentPositionY) {
9798            int positionX = parentPositionX + mPositionX;
9799            int positionY = parentPositionY + mPositionY;
9800
9801            positionY = clipVertically(positionY);
9802
9803            // Horizontal clipping
9804            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
9805            final int width = mContentView.getMeasuredWidth();
9806            positionX = Math.min(displayMetrics.widthPixels - width, positionX);
9807            positionX = Math.max(0, positionX);
9808
9809            if (isShowing()) {
9810                mPopupWindow.update(positionX, positionY, -1, -1);
9811            } else {
9812                mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
9813                        positionX, positionY);
9814            }
9815        }
9816
9817        public void hide() {
9818            mPopupWindow.dismiss();
9819            TextView.this.getPositionListener().removeSubscriber(this);
9820        }
9821
9822        @Override
9823        public void updatePosition(int parentPositionX, int parentPositionY,
9824                boolean parentPositionChanged, boolean parentScrolled) {
9825            // Either parentPositionChanged or parentScrolled is true, check if still visible
9826            if (isShowing() && isOffsetVisible(getTextOffset())) {
9827                if (parentScrolled) computeLocalPosition();
9828                updatePosition(parentPositionX, parentPositionY);
9829            } else {
9830                hide();
9831            }
9832        }
9833
9834        public boolean isShowing() {
9835            return mPopupWindow.isShowing();
9836        }
9837    }
9838
9839    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
9840        private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
9841        private static final int ADD_TO_DICTIONARY = -1;
9842        private static final int DELETE_TEXT = -2;
9843        private SuggestionInfo[] mSuggestionInfos;
9844        private int mNumberOfSuggestions;
9845        private boolean mCursorWasVisibleBeforeSuggestions;
9846        private boolean mIsShowingUp = false;
9847        private SuggestionAdapter mSuggestionsAdapter;
9848        private final Comparator<SuggestionSpan> mSuggestionSpanComparator;
9849        private final HashMap<SuggestionSpan, Integer> mSpansLengths;
9850
9851        private class CustomPopupWindow extends PopupWindow {
9852            public CustomPopupWindow(Context context, int defStyle) {
9853                super(context, null, defStyle);
9854            }
9855
9856            @Override
9857            public void dismiss() {
9858                super.dismiss();
9859
9860                TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
9861
9862                // Safe cast since show() checks that mText is an Editable
9863                ((Spannable) mText).removeSpan(getEditor().mSuggestionRangeSpan);
9864
9865                setCursorVisible(mCursorWasVisibleBeforeSuggestions);
9866                if (hasInsertionController()) {
9867                    getInsertionController().show();
9868                }
9869            }
9870        }
9871
9872        public SuggestionsPopupWindow() {
9873            mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
9874            mSuggestionSpanComparator = new SuggestionSpanComparator();
9875            mSpansLengths = new HashMap<SuggestionSpan, Integer>();
9876        }
9877
9878        @Override
9879        protected void createPopupWindow() {
9880            mPopupWindow = new CustomPopupWindow(TextView.this.mContext,
9881                com.android.internal.R.attr.textSuggestionsWindowStyle);
9882            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
9883            mPopupWindow.setFocusable(true);
9884            mPopupWindow.setClippingEnabled(false);
9885        }
9886
9887        @Override
9888        protected void initContentView() {
9889            ListView listView = new ListView(TextView.this.getContext());
9890            mSuggestionsAdapter = new SuggestionAdapter();
9891            listView.setAdapter(mSuggestionsAdapter);
9892            listView.setOnItemClickListener(this);
9893            mContentView = listView;
9894
9895            // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete
9896            mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2];
9897            for (int i = 0; i < mSuggestionInfos.length; i++) {
9898                mSuggestionInfos[i] = new SuggestionInfo();
9899            }
9900        }
9901
9902        public boolean isShowingUp() {
9903            return mIsShowingUp;
9904        }
9905
9906        public void onParentLostFocus() {
9907            mIsShowingUp = false;
9908        }
9909
9910        private class SuggestionInfo {
9911            int suggestionStart, suggestionEnd; // range of actual suggestion within text
9912            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
9913            int suggestionIndex; // the index of this suggestion inside suggestionSpan
9914            SpannableStringBuilder text = new SpannableStringBuilder();
9915            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
9916                    android.R.style.TextAppearance_SuggestionHighlight);
9917        }
9918
9919        private class SuggestionAdapter extends BaseAdapter {
9920            private LayoutInflater mInflater = (LayoutInflater) TextView.this.mContext.
9921                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
9922
9923            @Override
9924            public int getCount() {
9925                return mNumberOfSuggestions;
9926            }
9927
9928            @Override
9929            public Object getItem(int position) {
9930                return mSuggestionInfos[position];
9931            }
9932
9933            @Override
9934            public long getItemId(int position) {
9935                return position;
9936            }
9937
9938            @Override
9939            public View getView(int position, View convertView, ViewGroup parent) {
9940                TextView textView = (TextView) convertView;
9941
9942                if (textView == null) {
9943                    textView = (TextView) mInflater.inflate(mTextEditSuggestionItemLayout, parent,
9944                            false);
9945                }
9946
9947                final SuggestionInfo suggestionInfo = mSuggestionInfos[position];
9948                textView.setText(suggestionInfo.text);
9949
9950                if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
9951                    textView.setCompoundDrawablesWithIntrinsicBounds(
9952                            com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0);
9953                } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
9954                    textView.setCompoundDrawablesWithIntrinsicBounds(
9955                            com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0);
9956                } else {
9957                    textView.setCompoundDrawables(null, null, null, null);
9958                }
9959
9960                return textView;
9961            }
9962        }
9963
9964        private class SuggestionSpanComparator implements Comparator<SuggestionSpan> {
9965            public int compare(SuggestionSpan span1, SuggestionSpan span2) {
9966                final int flag1 = span1.getFlags();
9967                final int flag2 = span2.getFlags();
9968                if (flag1 != flag2) {
9969                    // The order here should match what is used in updateDrawState
9970                    final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
9971                    final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
9972                    final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0;
9973                    final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0;
9974                    if (easy1 && !misspelled1) return -1;
9975                    if (easy2 && !misspelled2) return 1;
9976                    if (misspelled1) return -1;
9977                    if (misspelled2) return 1;
9978                }
9979
9980                return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue();
9981            }
9982        }
9983
9984        /**
9985         * Returns the suggestion spans that cover the current cursor position. The suggestion
9986         * spans are sorted according to the length of text that they are attached to.
9987         */
9988        private SuggestionSpan[] getSuggestionSpans() {
9989            int pos = TextView.this.getSelectionStart();
9990            Spannable spannable = (Spannable) TextView.this.mText;
9991            SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
9992
9993            mSpansLengths.clear();
9994            for (SuggestionSpan suggestionSpan : suggestionSpans) {
9995                int start = spannable.getSpanStart(suggestionSpan);
9996                int end = spannable.getSpanEnd(suggestionSpan);
9997                mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start));
9998            }
9999
10000            // The suggestions are sorted according to their types (easy correction first, then
10001            // misspelled) and to the length of the text that they cover (shorter first).
10002            Arrays.sort(suggestionSpans, mSuggestionSpanComparator);
10003            return suggestionSpans;
10004        }
10005
10006        @Override
10007        public void show() {
10008            if (!(mText instanceof Editable)) return;
10009
10010            if (updateSuggestions()) {
10011                mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
10012                setCursorVisible(false);
10013                mIsShowingUp = true;
10014                super.show();
10015            }
10016        }
10017
10018        @Override
10019        protected void measureContent() {
10020            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
10021            final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
10022                    displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
10023            final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
10024                    displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
10025
10026            int width = 0;
10027            View view = null;
10028            for (int i = 0; i < mNumberOfSuggestions; i++) {
10029                view = mSuggestionsAdapter.getView(i, view, mContentView);
10030                view.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
10031                view.measure(horizontalMeasure, verticalMeasure);
10032                width = Math.max(width, view.getMeasuredWidth());
10033            }
10034
10035            // Enforce the width based on actual text widths
10036            mContentView.measure(
10037                    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
10038                    verticalMeasure);
10039
10040            Drawable popupBackground = mPopupWindow.getBackground();
10041            if (popupBackground != null) {
10042                if (mTempRect == null) mTempRect = new Rect();
10043                popupBackground.getPadding(mTempRect);
10044                width += mTempRect.left + mTempRect.right;
10045            }
10046            mPopupWindow.setWidth(width);
10047        }
10048
10049        @Override
10050        protected int getTextOffset() {
10051            return getSelectionStart();
10052        }
10053
10054        @Override
10055        protected int getVerticalLocalPosition(int line) {
10056            return mLayout.getLineBottom(line);
10057        }
10058
10059        @Override
10060        protected int clipVertically(int positionY) {
10061            final int height = mContentView.getMeasuredHeight();
10062            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
10063            return Math.min(positionY, displayMetrics.heightPixels - height);
10064        }
10065
10066        @Override
10067        public void hide() {
10068            super.hide();
10069        }
10070
10071        private boolean updateSuggestions() {
10072            Spannable spannable = (Spannable) TextView.this.mText;
10073            SuggestionSpan[] suggestionSpans = getSuggestionSpans();
10074
10075            final int nbSpans = suggestionSpans.length;
10076            // Suggestions are shown after a delay: the underlying spans may have been removed
10077            if (nbSpans == 0) return false;
10078
10079            mNumberOfSuggestions = 0;
10080            int spanUnionStart = mText.length();
10081            int spanUnionEnd = 0;
10082
10083            SuggestionSpan misspelledSpan = null;
10084            int underlineColor = 0;
10085
10086            for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
10087                SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
10088                final int spanStart = spannable.getSpanStart(suggestionSpan);
10089                final int spanEnd = spannable.getSpanEnd(suggestionSpan);
10090                spanUnionStart = Math.min(spanStart, spanUnionStart);
10091                spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
10092
10093                if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
10094                    misspelledSpan = suggestionSpan;
10095                }
10096
10097                // The first span dictates the background color of the highlighted text
10098                if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor();
10099
10100                String[] suggestions = suggestionSpan.getSuggestions();
10101                int nbSuggestions = suggestions.length;
10102                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
10103                    String suggestion = suggestions[suggestionIndex];
10104
10105                    boolean suggestionIsDuplicate = false;
10106                    for (int i = 0; i < mNumberOfSuggestions; i++) {
10107                        if (mSuggestionInfos[i].text.toString().equals(suggestion)) {
10108                            SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan;
10109                            final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan);
10110                            final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan);
10111                            if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
10112                                suggestionIsDuplicate = true;
10113                                break;
10114                            }
10115                        }
10116                    }
10117
10118                    if (!suggestionIsDuplicate) {
10119                        SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
10120                        suggestionInfo.suggestionSpan = suggestionSpan;
10121                        suggestionInfo.suggestionIndex = suggestionIndex;
10122                        suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion);
10123
10124                        mNumberOfSuggestions++;
10125
10126                        if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
10127                            // Also end outer for loop
10128                            spanIndex = nbSpans;
10129                            break;
10130                        }
10131                    }
10132                }
10133            }
10134
10135            for (int i = 0; i < mNumberOfSuggestions; i++) {
10136                highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
10137            }
10138
10139            // Add "Add to dictionary" item if there is a span with the misspelled flag
10140            if (misspelledSpan != null) {
10141                final int misspelledStart = spannable.getSpanStart(misspelledSpan);
10142                final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
10143                if (misspelledStart >= 0 && misspelledEnd > misspelledStart) {
10144                    SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
10145                    suggestionInfo.suggestionSpan = misspelledSpan;
10146                    suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY;
10147                    suggestionInfo.text.replace(0, suggestionInfo.text.length(),
10148                            getContext().getString(com.android.internal.R.string.addToDictionary));
10149                    suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
10150                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10151
10152                    mNumberOfSuggestions++;
10153                }
10154            }
10155
10156            // Delete item
10157            SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
10158            suggestionInfo.suggestionSpan = null;
10159            suggestionInfo.suggestionIndex = DELETE_TEXT;
10160            suggestionInfo.text.replace(0, suggestionInfo.text.length(),
10161                    getContext().getString(com.android.internal.R.string.deleteText));
10162            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
10163                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10164            mNumberOfSuggestions++;
10165
10166            if (getEditor().mSuggestionRangeSpan == null) getEditor().mSuggestionRangeSpan = new SuggestionRangeSpan();
10167            if (underlineColor == 0) {
10168                // Fallback on the default highlight color when the first span does not provide one
10169                getEditor().mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
10170            } else {
10171                final float BACKGROUND_TRANSPARENCY = 0.4f;
10172                final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
10173                getEditor().mSuggestionRangeSpan.setBackgroundColor(
10174                        (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
10175            }
10176            spannable.setSpan(getEditor().mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
10177                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10178
10179            mSuggestionsAdapter.notifyDataSetChanged();
10180            return true;
10181        }
10182
10183        private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
10184                int unionEnd) {
10185            final Spannable text = (Spannable) mText;
10186            final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan);
10187            final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan);
10188
10189            // Adjust the start/end of the suggestion span
10190            suggestionInfo.suggestionStart = spanStart - unionStart;
10191            suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart
10192                    + suggestionInfo.text.length();
10193
10194            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0,
10195                    suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
10196
10197            // Add the text before and after the span.
10198            final String textAsString = text.toString();
10199            suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart));
10200            suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd));
10201        }
10202
10203        @Override
10204        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
10205            Editable editable = (Editable) mText;
10206            SuggestionInfo suggestionInfo = mSuggestionInfos[position];
10207
10208            if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
10209                final int spanUnionStart = editable.getSpanStart(getEditor().mSuggestionRangeSpan);
10210                int spanUnionEnd = editable.getSpanEnd(getEditor().mSuggestionRangeSpan);
10211                if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) {
10212                    // Do not leave two adjacent spaces after deletion, or one at beginning of text
10213                    if (spanUnionEnd < editable.length() &&
10214                            Character.isSpaceChar(editable.charAt(spanUnionEnd)) &&
10215                            (spanUnionStart == 0 ||
10216                            Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) {
10217                        spanUnionEnd = spanUnionEnd + 1;
10218                    }
10219                    deleteText_internal(spanUnionStart, spanUnionEnd);
10220                }
10221                hide();
10222                return;
10223            }
10224
10225            final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan);
10226            final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
10227            if (spanStart < 0 || spanEnd <= spanStart) {
10228                // Span has been removed
10229                hide();
10230                return;
10231            }
10232            final String originalText = mText.toString().substring(spanStart, spanEnd);
10233
10234            if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
10235                Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
10236                intent.putExtra("word", originalText);
10237                intent.putExtra("locale", getTextServicesLocale().toString());
10238                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
10239                getContext().startActivity(intent);
10240                // There is no way to know if the word was indeed added. Re-check.
10241                // TODO The ExtractEditText should remove the span in the original text instead
10242                editable.removeSpan(suggestionInfo.suggestionSpan);
10243                updateSpellCheckSpans(spanStart, spanEnd, false);
10244            } else {
10245                // SuggestionSpans are removed by replace: save them before
10246                SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
10247                        SuggestionSpan.class);
10248                final int length = suggestionSpans.length;
10249                int[] suggestionSpansStarts = new int[length];
10250                int[] suggestionSpansEnds = new int[length];
10251                int[] suggestionSpansFlags = new int[length];
10252                for (int i = 0; i < length; i++) {
10253                    final SuggestionSpan suggestionSpan = suggestionSpans[i];
10254                    suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
10255                    suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
10256                    suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
10257
10258                    // Remove potential misspelled flags
10259                    int suggestionSpanFlags = suggestionSpan.getFlags();
10260                    if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
10261                        suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
10262                        suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
10263                        suggestionSpan.setFlags(suggestionSpanFlags);
10264                    }
10265                }
10266
10267                final int suggestionStart = suggestionInfo.suggestionStart;
10268                final int suggestionEnd = suggestionInfo.suggestionEnd;
10269                final String suggestion = suggestionInfo.text.subSequence(
10270                        suggestionStart, suggestionEnd).toString();
10271                replaceText_internal(spanStart, spanEnd, suggestion);
10272
10273                // Notify source IME of the suggestion pick. Do this before swaping texts.
10274                if (!TextUtils.isEmpty(
10275                        suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
10276                    InputMethodManager imm = InputMethodManager.peekInstance();
10277                    if (imm != null) {
10278                        imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
10279                                suggestionInfo.suggestionIndex);
10280                    }
10281                }
10282
10283                // Swap text content between actual text and Suggestion span
10284                String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
10285                suggestions[suggestionInfo.suggestionIndex] = originalText;
10286
10287                // Restore previous SuggestionSpans
10288                final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
10289                for (int i = 0; i < length; i++) {
10290                    // Only spans that include the modified region make sense after replacement
10291                    // Spans partially included in the replaced region are removed, there is no
10292                    // way to assign them a valid range after replacement
10293                    if (suggestionSpansStarts[i] <= spanStart &&
10294                            suggestionSpansEnds[i] >= spanEnd) {
10295                        setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i],
10296                                suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]);
10297                    }
10298                }
10299
10300                // Move cursor at the end of the replaced word
10301                final int newCursorPosition = spanEnd + lengthDifference;
10302                setCursorPosition_internal(newCursorPosition, newCursorPosition);
10303            }
10304
10305            hide();
10306        }
10307    }
10308
10309    /**
10310     * An ActionMode Callback class that is used to provide actions while in text selection mode.
10311     *
10312     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
10313     * on which of these this TextView supports.
10314     */
10315    private class SelectionActionModeCallback implements ActionMode.Callback {
10316
10317        @Override
10318        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
10319            TypedArray styledAttributes = mContext.obtainStyledAttributes(
10320                    com.android.internal.R.styleable.SelectionModeDrawables);
10321
10322            boolean allowText = getContext().getResources().getBoolean(
10323                    com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
10324
10325            mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
10326            mode.setSubtitle(null);
10327            mode.setTitleOptionalHint(true);
10328
10329            int selectAllIconId = 0; // No icon by default
10330            if (!allowText) {
10331                // Provide an icon, text will not be displayed on smaller screens.
10332                selectAllIconId = styledAttributes.getResourceId(
10333                        R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0);
10334            }
10335
10336            menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
10337                    setIcon(selectAllIconId).
10338                    setAlphabeticShortcut('a').
10339                    setShowAsAction(
10340                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10341
10342            if (canCut()) {
10343                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
10344                    setIcon(styledAttributes.getResourceId(
10345                            R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)).
10346                    setAlphabeticShortcut('x').
10347                    setShowAsAction(
10348                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10349            }
10350
10351            if (canCopy()) {
10352                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
10353                    setIcon(styledAttributes.getResourceId(
10354                            R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)).
10355                    setAlphabeticShortcut('c').
10356                    setShowAsAction(
10357                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10358            }
10359
10360            if (canPaste()) {
10361                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
10362                        setIcon(styledAttributes.getResourceId(
10363                                R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)).
10364                        setAlphabeticShortcut('v').
10365                        setShowAsAction(
10366                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
10367            }
10368
10369            styledAttributes.recycle();
10370
10371            if (getEditor().mCustomSelectionActionModeCallback != null) {
10372                if (!getEditor().mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
10373                    // The custom mode can choose to cancel the action mode
10374                    return false;
10375                }
10376            }
10377
10378            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
10379                getSelectionController().show();
10380                return true;
10381            } else {
10382                return false;
10383            }
10384        }
10385
10386        @Override
10387        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
10388            if (getEditor().mCustomSelectionActionModeCallback != null) {
10389                return getEditor().mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
10390            }
10391            return true;
10392        }
10393
10394        @Override
10395        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
10396            if (getEditor().mCustomSelectionActionModeCallback != null &&
10397                 getEditor().mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
10398                return true;
10399            }
10400            return onTextContextMenuItem(item.getItemId());
10401        }
10402
10403        @Override
10404        public void onDestroyActionMode(ActionMode mode) {
10405            if (getEditor().mCustomSelectionActionModeCallback != null) {
10406                getEditor().mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
10407            }
10408            Selection.setSelection((Spannable) mText, getSelectionEnd());
10409
10410            if (getEditor().mSelectionModifierCursorController != null) {
10411                getEditor().mSelectionModifierCursorController.hide();
10412            }
10413
10414            getEditor().mSelectionActionMode = null;
10415        }
10416    }
10417
10418    private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
10419        private static final int POPUP_TEXT_LAYOUT =
10420                com.android.internal.R.layout.text_edit_action_popup_text;
10421        private TextView mPasteTextView;
10422        private TextView mReplaceTextView;
10423
10424        @Override
10425        protected void createPopupWindow() {
10426            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
10427                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10428            mPopupWindow.setClippingEnabled(true);
10429        }
10430
10431        @Override
10432        protected void initContentView() {
10433            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
10434            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
10435            mContentView = linearLayout;
10436            mContentView.setBackgroundResource(
10437                    com.android.internal.R.drawable.text_edit_paste_window);
10438
10439            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
10440                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
10441
10442            LayoutParams wrapContent = new LayoutParams(
10443                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
10444
10445            mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
10446            mPasteTextView.setLayoutParams(wrapContent);
10447            mContentView.addView(mPasteTextView);
10448            mPasteTextView.setText(com.android.internal.R.string.paste);
10449            mPasteTextView.setOnClickListener(this);
10450
10451            mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
10452            mReplaceTextView.setLayoutParams(wrapContent);
10453            mContentView.addView(mReplaceTextView);
10454            mReplaceTextView.setText(com.android.internal.R.string.replace);
10455            mReplaceTextView.setOnClickListener(this);
10456        }
10457
10458        @Override
10459        public void show() {
10460            boolean canPaste = canPaste();
10461            boolean canSuggest = isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
10462            mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE);
10463            mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE);
10464
10465            if (!canPaste && !canSuggest) return;
10466
10467            super.show();
10468        }
10469
10470        @Override
10471        public void onClick(View view) {
10472            if (view == mPasteTextView && canPaste()) {
10473                onTextContextMenuItem(ID_PASTE);
10474                hide();
10475            } else if (view == mReplaceTextView) {
10476                final int middle = (getSelectionStart() + getSelectionEnd()) / 2;
10477                stopSelectionActionMode();
10478                Selection.setSelection((Spannable) mText, middle);
10479                showSuggestions();
10480            }
10481        }
10482
10483        @Override
10484        protected int getTextOffset() {
10485            return (getSelectionStart() + getSelectionEnd()) / 2;
10486        }
10487
10488        @Override
10489        protected int getVerticalLocalPosition(int line) {
10490            return mLayout.getLineTop(line) - mContentView.getMeasuredHeight();
10491        }
10492
10493        @Override
10494        protected int clipVertically(int positionY) {
10495            if (positionY < 0) {
10496                final int offset = getTextOffset();
10497                final int line = mLayout.getLineForOffset(offset);
10498                positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line);
10499                positionY += mContentView.getMeasuredHeight();
10500
10501                // Assumes insertion and selection handles share the same height
10502                final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
10503                positionY += handle.getIntrinsicHeight();
10504            }
10505
10506            return positionY;
10507        }
10508    }
10509
10510    private abstract class HandleView extends View implements TextViewPositionListener {
10511        protected Drawable mDrawable;
10512        protected Drawable mDrawableLtr;
10513        protected Drawable mDrawableRtl;
10514        private final PopupWindow mContainer;
10515        // Position with respect to the parent TextView
10516        private int mPositionX, mPositionY;
10517        private boolean mIsDragging;
10518        // Offset from touch position to mPosition
10519        private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
10520        protected int mHotspotX;
10521        // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
10522        private float mTouchOffsetY;
10523        // Where the touch position should be on the handle to ensure a maximum cursor visibility
10524        private float mIdealVerticalOffset;
10525        // Parent's (TextView) previous position in window
10526        private int mLastParentX, mLastParentY;
10527        // Transient action popup window for Paste and Replace actions
10528        protected ActionPopupWindow mActionPopupWindow;
10529        // Previous text character offset
10530        private int mPreviousOffset = -1;
10531        // Previous text character offset
10532        private boolean mPositionHasChanged = true;
10533        // Used to delay the appearance of the action popup window
10534        private Runnable mActionPopupShower;
10535
10536        public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
10537            super(TextView.this.mContext);
10538            mContainer = new PopupWindow(TextView.this.mContext, null,
10539                    com.android.internal.R.attr.textSelectHandleWindowStyle);
10540            mContainer.setSplitTouchEnabled(true);
10541            mContainer.setClippingEnabled(false);
10542            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
10543            mContainer.setContentView(this);
10544
10545            mDrawableLtr = drawableLtr;
10546            mDrawableRtl = drawableRtl;
10547
10548            updateDrawable();
10549
10550            final int handleHeight = mDrawable.getIntrinsicHeight();
10551            mTouchOffsetY = -0.3f * handleHeight;
10552            mIdealVerticalOffset = 0.7f * handleHeight;
10553        }
10554
10555        protected void updateDrawable() {
10556            final int offset = getCurrentCursorOffset();
10557            final boolean isRtlCharAtOffset = mLayout.isRtlCharAt(offset);
10558            mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
10559            mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
10560        }
10561
10562        protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
10563
10564        // Touch-up filter: number of previous positions remembered
10565        private static final int HISTORY_SIZE = 5;
10566        private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
10567        private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
10568        private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
10569        private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
10570        private int mPreviousOffsetIndex = 0;
10571        private int mNumberPreviousOffsets = 0;
10572
10573        private void startTouchUpFilter(int offset) {
10574            mNumberPreviousOffsets = 0;
10575            addPositionToTouchUpFilter(offset);
10576        }
10577
10578        private void addPositionToTouchUpFilter(int offset) {
10579            mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
10580            mPreviousOffsets[mPreviousOffsetIndex] = offset;
10581            mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
10582            mNumberPreviousOffsets++;
10583        }
10584
10585        private void filterOnTouchUp() {
10586            final long now = SystemClock.uptimeMillis();
10587            int i = 0;
10588            int index = mPreviousOffsetIndex;
10589            final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
10590            while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
10591                i++;
10592                index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
10593            }
10594
10595            if (i > 0 && i < iMax &&
10596                    (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
10597                positionAtCursorOffset(mPreviousOffsets[index], false);
10598            }
10599        }
10600
10601        public boolean offsetHasBeenChanged() {
10602            return mNumberPreviousOffsets > 1;
10603        }
10604
10605        @Override
10606        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
10607            setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
10608        }
10609
10610        public void show() {
10611            if (isShowing()) return;
10612
10613            getPositionListener().addSubscriber(this, true /* local position may change */);
10614
10615            // Make sure the offset is always considered new, even when focusing at same position
10616            mPreviousOffset = -1;
10617            positionAtCursorOffset(getCurrentCursorOffset(), false);
10618
10619            hideActionPopupWindow();
10620        }
10621
10622        protected void dismiss() {
10623            mIsDragging = false;
10624            mContainer.dismiss();
10625            onDetached();
10626        }
10627
10628        public void hide() {
10629            dismiss();
10630
10631            TextView.this.getPositionListener().removeSubscriber(this);
10632        }
10633
10634        void showActionPopupWindow(int delay) {
10635            if (mActionPopupWindow == null) {
10636                mActionPopupWindow = new ActionPopupWindow();
10637            }
10638            if (mActionPopupShower == null) {
10639                mActionPopupShower = new Runnable() {
10640                    public void run() {
10641                        mActionPopupWindow.show();
10642                    }
10643                };
10644            } else {
10645                TextView.this.removeCallbacks(mActionPopupShower);
10646            }
10647            TextView.this.postDelayed(mActionPopupShower, delay);
10648        }
10649
10650        protected void hideActionPopupWindow() {
10651            if (mActionPopupShower != null) {
10652                TextView.this.removeCallbacks(mActionPopupShower);
10653            }
10654            if (mActionPopupWindow != null) {
10655                mActionPopupWindow.hide();
10656            }
10657        }
10658
10659        public boolean isShowing() {
10660            return mContainer.isShowing();
10661        }
10662
10663        private boolean isVisible() {
10664            // Always show a dragging handle.
10665            if (mIsDragging) {
10666                return true;
10667            }
10668
10669            if (isInBatchEditMode()) {
10670                return false;
10671            }
10672
10673            return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY);
10674        }
10675
10676        public abstract int getCurrentCursorOffset();
10677
10678        protected abstract void updateSelection(int offset);
10679
10680        public abstract void updatePosition(float x, float y);
10681
10682        protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
10683            // A HandleView relies on the layout, which may be nulled by external methods
10684            if (mLayout == null) {
10685                // Will update controllers' state, hiding them and stopping selection mode if needed
10686                prepareCursorControllers();
10687                return;
10688            }
10689
10690            boolean offsetChanged = offset != mPreviousOffset;
10691            if (offsetChanged || parentScrolled) {
10692                if (offsetChanged) {
10693                    updateSelection(offset);
10694                    addPositionToTouchUpFilter(offset);
10695                }
10696                final int line = mLayout.getLineForOffset(offset);
10697
10698                mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
10699                mPositionY = mLayout.getLineBottom(line);
10700
10701                // Take TextView's padding and scroll into account.
10702                mPositionX += viewportToContentHorizontalOffset();
10703                mPositionY += viewportToContentVerticalOffset();
10704
10705                mPreviousOffset = offset;
10706                mPositionHasChanged = true;
10707            }
10708        }
10709
10710        public void updatePosition(int parentPositionX, int parentPositionY,
10711                boolean parentPositionChanged, boolean parentScrolled) {
10712            positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
10713            if (parentPositionChanged || mPositionHasChanged) {
10714                if (mIsDragging) {
10715                    // Update touchToWindow offset in case of parent scrolling while dragging
10716                    if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
10717                        mTouchToWindowOffsetX += parentPositionX - mLastParentX;
10718                        mTouchToWindowOffsetY += parentPositionY - mLastParentY;
10719                        mLastParentX = parentPositionX;
10720                        mLastParentY = parentPositionY;
10721                    }
10722
10723                    onHandleMoved();
10724                }
10725
10726                if (isVisible()) {
10727                    final int positionX = parentPositionX + mPositionX;
10728                    final int positionY = parentPositionY + mPositionY;
10729                    if (isShowing()) {
10730                        mContainer.update(positionX, positionY, -1, -1);
10731                    } else {
10732                        mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
10733                                positionX, positionY);
10734                    }
10735                } else {
10736                    if (isShowing()) {
10737                        dismiss();
10738                    }
10739                }
10740
10741                mPositionHasChanged = false;
10742            }
10743        }
10744
10745        @Override
10746        protected void onDraw(Canvas c) {
10747            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
10748            mDrawable.draw(c);
10749        }
10750
10751        @Override
10752        public boolean onTouchEvent(MotionEvent ev) {
10753            switch (ev.getActionMasked()) {
10754                case MotionEvent.ACTION_DOWN: {
10755                    startTouchUpFilter(getCurrentCursorOffset());
10756                    mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
10757                    mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
10758
10759                    final PositionListener positionListener = getPositionListener();
10760                    mLastParentX = positionListener.getPositionX();
10761                    mLastParentY = positionListener.getPositionY();
10762                    mIsDragging = true;
10763                    break;
10764                }
10765
10766                case MotionEvent.ACTION_MOVE: {
10767                    final float rawX = ev.getRawX();
10768                    final float rawY = ev.getRawY();
10769
10770                    // Vertical hysteresis: vertical down movement tends to snap to ideal offset
10771                    final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
10772                    final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
10773                    float newVerticalOffset;
10774                    if (previousVerticalOffset < mIdealVerticalOffset) {
10775                        newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
10776                        newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
10777                    } else {
10778                        newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
10779                        newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
10780                    }
10781                    mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
10782
10783                    final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
10784                    final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
10785
10786                    updatePosition(newPosX, newPosY);
10787                    break;
10788                }
10789
10790                case MotionEvent.ACTION_UP:
10791                    filterOnTouchUp();
10792                    mIsDragging = false;
10793                    break;
10794
10795                case MotionEvent.ACTION_CANCEL:
10796                    mIsDragging = false;
10797                    break;
10798            }
10799            return true;
10800        }
10801
10802        public boolean isDragging() {
10803            return mIsDragging;
10804        }
10805
10806        void onHandleMoved() {
10807            hideActionPopupWindow();
10808        }
10809
10810        public void onDetached() {
10811            hideActionPopupWindow();
10812        }
10813    }
10814
10815    private class InsertionHandleView extends HandleView {
10816        private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
10817        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
10818
10819        // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow
10820        private float mDownPositionX, mDownPositionY;
10821        private Runnable mHider;
10822
10823        public InsertionHandleView(Drawable drawable) {
10824            super(drawable, drawable);
10825        }
10826
10827        @Override
10828        public void show() {
10829            super.show();
10830
10831            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - LAST_CUT_OR_COPY_TIME;
10832            if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
10833                showActionPopupWindow(0);
10834            }
10835
10836            hideAfterDelay();
10837        }
10838
10839        public void showWithActionPopup() {
10840            show();
10841            showActionPopupWindow(0);
10842        }
10843
10844        private void hideAfterDelay() {
10845            if (mHider == null) {
10846                mHider = new Runnable() {
10847                    public void run() {
10848                        hide();
10849                    }
10850                };
10851            } else {
10852                removeHiderCallback();
10853            }
10854            TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
10855        }
10856
10857        private void removeHiderCallback() {
10858            if (mHider != null) {
10859                TextView.this.removeCallbacks(mHider);
10860            }
10861        }
10862
10863        @Override
10864        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10865            return drawable.getIntrinsicWidth() / 2;
10866        }
10867
10868        @Override
10869        public boolean onTouchEvent(MotionEvent ev) {
10870            final boolean result = super.onTouchEvent(ev);
10871
10872            switch (ev.getActionMasked()) {
10873                case MotionEvent.ACTION_DOWN:
10874                    mDownPositionX = ev.getRawX();
10875                    mDownPositionY = ev.getRawY();
10876                    break;
10877
10878                case MotionEvent.ACTION_UP:
10879                    if (!offsetHasBeenChanged()) {
10880                        final float deltaX = mDownPositionX - ev.getRawX();
10881                        final float deltaY = mDownPositionY - ev.getRawY();
10882                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
10883
10884                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
10885                                TextView.this.getContext());
10886                        final int touchSlop = viewConfiguration.getScaledTouchSlop();
10887
10888                        if (distanceSquared < touchSlop * touchSlop) {
10889                            if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
10890                                // Tapping on the handle dismisses the displayed action popup
10891                                mActionPopupWindow.hide();
10892                            } else {
10893                                showWithActionPopup();
10894                            }
10895                        }
10896                    }
10897                    hideAfterDelay();
10898                    break;
10899
10900                case MotionEvent.ACTION_CANCEL:
10901                    hideAfterDelay();
10902                    break;
10903
10904                default:
10905                    break;
10906            }
10907
10908            return result;
10909        }
10910
10911        @Override
10912        public int getCurrentCursorOffset() {
10913            return TextView.this.getSelectionStart();
10914        }
10915
10916        @Override
10917        public void updateSelection(int offset) {
10918            Selection.setSelection((Spannable) mText, offset);
10919        }
10920
10921        @Override
10922        public void updatePosition(float x, float y) {
10923            positionAtCursorOffset(getOffsetForPosition(x, y), false);
10924        }
10925
10926        @Override
10927        void onHandleMoved() {
10928            super.onHandleMoved();
10929            removeHiderCallback();
10930        }
10931
10932        @Override
10933        public void onDetached() {
10934            super.onDetached();
10935            removeHiderCallback();
10936        }
10937    }
10938
10939    private class SelectionStartHandleView extends HandleView {
10940
10941        public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
10942            super(drawableLtr, drawableRtl);
10943        }
10944
10945        @Override
10946        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10947            if (isRtlRun) {
10948                return drawable.getIntrinsicWidth() / 4;
10949            } else {
10950                return (drawable.getIntrinsicWidth() * 3) / 4;
10951            }
10952        }
10953
10954        @Override
10955        public int getCurrentCursorOffset() {
10956            return TextView.this.getSelectionStart();
10957        }
10958
10959        @Override
10960        public void updateSelection(int offset) {
10961            Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
10962            updateDrawable();
10963        }
10964
10965        @Override
10966        public void updatePosition(float x, float y) {
10967            int offset = getOffsetForPosition(x, y);
10968
10969            // Handles can not cross and selection is at least one character
10970            final int selectionEnd = getSelectionEnd();
10971            if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
10972
10973            positionAtCursorOffset(offset, false);
10974        }
10975
10976        public ActionPopupWindow getActionPopupWindow() {
10977            return mActionPopupWindow;
10978        }
10979    }
10980
10981    private class SelectionEndHandleView extends HandleView {
10982
10983        public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
10984            super(drawableLtr, drawableRtl);
10985        }
10986
10987        @Override
10988        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
10989            if (isRtlRun) {
10990                return (drawable.getIntrinsicWidth() * 3) / 4;
10991            } else {
10992                return drawable.getIntrinsicWidth() / 4;
10993            }
10994        }
10995
10996        @Override
10997        public int getCurrentCursorOffset() {
10998            return TextView.this.getSelectionEnd();
10999        }
11000
11001        @Override
11002        public void updateSelection(int offset) {
11003            Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
11004            updateDrawable();
11005        }
11006
11007        @Override
11008        public void updatePosition(float x, float y) {
11009            int offset = getOffsetForPosition(x, y);
11010
11011            // Handles can not cross and selection is at least one character
11012            final int selectionStart = getSelectionStart();
11013            if (offset <= selectionStart) offset = Math.min(selectionStart + 1, mText.length());
11014
11015            positionAtCursorOffset(offset, false);
11016        }
11017
11018        public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
11019            mActionPopupWindow = actionPopupWindow;
11020        }
11021    }
11022
11023    /**
11024     * A CursorController instance can be used to control a cursor in the text.
11025     * It is not used outside of {@link TextView}.
11026     * @hide
11027     */
11028    private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
11029        /**
11030         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
11031         * See also {@link #hide()}.
11032         */
11033        public void show();
11034
11035        /**
11036         * Hide the cursor controller from screen.
11037         * See also {@link #show()}.
11038         */
11039        public void hide();
11040
11041        /**
11042         * Called when the view is detached from window. Perform house keeping task, such as
11043         * stopping Runnable thread that would otherwise keep a reference on the context, thus
11044         * preventing the activity from being recycled.
11045         */
11046        public void onDetached();
11047    }
11048
11049    private class InsertionPointCursorController implements CursorController {
11050        private InsertionHandleView mHandle;
11051
11052        public void show() {
11053            getHandle().show();
11054        }
11055
11056        public void showWithActionPopup() {
11057            getHandle().showWithActionPopup();
11058        }
11059
11060        public void hide() {
11061            if (mHandle != null) {
11062                mHandle.hide();
11063            }
11064        }
11065
11066        public void onTouchModeChanged(boolean isInTouchMode) {
11067            if (!isInTouchMode) {
11068                hide();
11069            }
11070        }
11071
11072        private InsertionHandleView getHandle() {
11073            if (getEditor().mSelectHandleCenter == null) {
11074                getEditor().mSelectHandleCenter = mContext.getResources().getDrawable(
11075                        mTextSelectHandleRes);
11076            }
11077            if (mHandle == null) {
11078                mHandle = new InsertionHandleView(getEditor().mSelectHandleCenter);
11079            }
11080            return mHandle;
11081        }
11082
11083        @Override
11084        public void onDetached() {
11085            final ViewTreeObserver observer = getViewTreeObserver();
11086            observer.removeOnTouchModeChangeListener(this);
11087
11088            if (mHandle != null) mHandle.onDetached();
11089        }
11090    }
11091
11092    private class SelectionModifierCursorController implements CursorController {
11093        private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds
11094        // The cursor controller handles, lazily created when shown.
11095        private SelectionStartHandleView mStartHandle;
11096        private SelectionEndHandleView mEndHandle;
11097        // The offsets of that last touch down event. Remembered to start selection there.
11098        private int mMinTouchOffset, mMaxTouchOffset;
11099
11100        // Double tap detection
11101        private long mPreviousTapUpTime = 0;
11102        private float mDownPositionX, mDownPositionY;
11103        private boolean mGestureStayedInTapRegion;
11104
11105        SelectionModifierCursorController() {
11106            resetTouchOffsets();
11107        }
11108
11109        public void show() {
11110            if (isInBatchEditMode()) {
11111                return;
11112            }
11113            initDrawables();
11114            initHandles();
11115            hideInsertionPointCursorController();
11116        }
11117
11118        private void initDrawables() {
11119            if (getEditor().mSelectHandleLeft == null) {
11120                getEditor().mSelectHandleLeft = mContext.getResources().getDrawable(
11121                        mTextSelectHandleLeftRes);
11122            }
11123            if (getEditor().mSelectHandleRight == null) {
11124                getEditor().mSelectHandleRight = mContext.getResources().getDrawable(
11125                        mTextSelectHandleRightRes);
11126            }
11127        }
11128
11129        private void initHandles() {
11130            // Lazy object creation has to be done before updatePosition() is called.
11131            if (mStartHandle == null) {
11132                mStartHandle = new SelectionStartHandleView(getEditor().mSelectHandleLeft, getEditor().mSelectHandleRight);
11133            }
11134            if (mEndHandle == null) {
11135                mEndHandle = new SelectionEndHandleView(getEditor().mSelectHandleRight, getEditor().mSelectHandleLeft);
11136            }
11137
11138            mStartHandle.show();
11139            mEndHandle.show();
11140
11141            // Make sure both left and right handles share the same ActionPopupWindow (so that
11142            // moving any of the handles hides the action popup).
11143            mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION);
11144            mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
11145
11146            hideInsertionPointCursorController();
11147        }
11148
11149        public void hide() {
11150            if (mStartHandle != null) mStartHandle.hide();
11151            if (mEndHandle != null) mEndHandle.hide();
11152        }
11153
11154        public void onTouchEvent(MotionEvent event) {
11155            // This is done even when the View does not have focus, so that long presses can start
11156            // selection and tap can move cursor from this tap position.
11157            switch (event.getActionMasked()) {
11158                case MotionEvent.ACTION_DOWN:
11159                    final float x = event.getX();
11160                    final float y = event.getY();
11161
11162                    // Remember finger down position, to be able to start selection from there
11163                    mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
11164
11165                    // Double tap detection
11166                    if (mGestureStayedInTapRegion) {
11167                        long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
11168                        if (duration <= ViewConfiguration.getDoubleTapTimeout()) {
11169                            final float deltaX = x - mDownPositionX;
11170                            final float deltaY = y - mDownPositionY;
11171                            final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
11172
11173                            ViewConfiguration viewConfiguration = ViewConfiguration.get(
11174                                    TextView.this.getContext());
11175                            int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
11176                            boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop;
11177
11178                            if (stayedInArea && isPositionOnText(x, y)) {
11179                                startSelectionActionMode();
11180                                getEditor().mDiscardNextActionUp = true;
11181                            }
11182                        }
11183                    }
11184
11185                    mDownPositionX = x;
11186                    mDownPositionY = y;
11187                    mGestureStayedInTapRegion = true;
11188                    break;
11189
11190                case MotionEvent.ACTION_POINTER_DOWN:
11191                case MotionEvent.ACTION_POINTER_UP:
11192                    // Handle multi-point gestures. Keep min and max offset positions.
11193                    // Only activated for devices that correctly handle multi-touch.
11194                    if (mContext.getPackageManager().hasSystemFeature(
11195                            PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
11196                        updateMinAndMaxOffsets(event);
11197                    }
11198                    break;
11199
11200                case MotionEvent.ACTION_MOVE:
11201                    if (mGestureStayedInTapRegion) {
11202                        final float deltaX = event.getX() - mDownPositionX;
11203                        final float deltaY = event.getY() - mDownPositionY;
11204                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
11205
11206                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
11207                                TextView.this.getContext());
11208                        int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
11209
11210                        if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
11211                            mGestureStayedInTapRegion = false;
11212                        }
11213                    }
11214                    break;
11215
11216                case MotionEvent.ACTION_UP:
11217                    mPreviousTapUpTime = SystemClock.uptimeMillis();
11218                    break;
11219            }
11220        }
11221
11222        /**
11223         * @param event
11224         */
11225        private void updateMinAndMaxOffsets(MotionEvent event) {
11226            int pointerCount = event.getPointerCount();
11227            for (int index = 0; index < pointerCount; index++) {
11228                int offset = getOffsetForPosition(event.getX(index), event.getY(index));
11229                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
11230                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
11231            }
11232        }
11233
11234        public int getMinTouchOffset() {
11235            return mMinTouchOffset;
11236        }
11237
11238        public int getMaxTouchOffset() {
11239            return mMaxTouchOffset;
11240        }
11241
11242        public void resetTouchOffsets() {
11243            mMinTouchOffset = mMaxTouchOffset = -1;
11244        }
11245
11246        /**
11247         * @return true iff this controller is currently used to move the selection start.
11248         */
11249        public boolean isSelectionStartDragged() {
11250            return mStartHandle != null && mStartHandle.isDragging();
11251        }
11252
11253        public void onTouchModeChanged(boolean isInTouchMode) {
11254            if (!isInTouchMode) {
11255                hide();
11256            }
11257        }
11258
11259        @Override
11260        public void onDetached() {
11261            final ViewTreeObserver observer = getViewTreeObserver();
11262            observer.removeOnTouchModeChangeListener(this);
11263
11264            if (mStartHandle != null) mStartHandle.onDetached();
11265            if (mEndHandle != null) mEndHandle.onDetached();
11266        }
11267    }
11268
11269    static class InputContentType {
11270        int imeOptions = EditorInfo.IME_NULL;
11271        String privateImeOptions;
11272        CharSequence imeActionLabel;
11273        int imeActionId;
11274        Bundle extras;
11275        OnEditorActionListener onEditorActionListener;
11276        boolean enterDown;
11277    }
11278
11279    static class InputMethodState {
11280        Rect mCursorRectInWindow = new Rect();
11281        RectF mTmpRectF = new RectF();
11282        float[] mTmpOffset = new float[2];
11283        ExtractedTextRequest mExtracting;
11284        final ExtractedText mTmpExtracted = new ExtractedText();
11285        int mBatchEditNesting;
11286        boolean mCursorChanged;
11287        boolean mSelectionModeChanged;
11288        boolean mContentChanged;
11289        int mChangedStart, mChangedEnd, mChangedDelta;
11290    }
11291
11292    private class Editor {
11293        // Cursor Controllers.
11294        InsertionPointCursorController mInsertionPointCursorController;
11295        SelectionModifierCursorController mSelectionModifierCursorController;
11296        ActionMode mSelectionActionMode;
11297        boolean mInsertionControllerEnabled;
11298        boolean mSelectionControllerEnabled;
11299
11300        // Used to highlight a word when it is corrected by the IME
11301        CorrectionHighlighter mCorrectionHighlighter;
11302
11303        InputContentType mInputContentType;
11304        InputMethodState mInputMethodState;
11305
11306        DisplayList mTextDisplayList;
11307        boolean mTextDisplayListIsValid;
11308
11309        boolean mFrozenWithFocus;
11310        boolean mSelectionMoved;
11311        boolean mTouchFocusSelected;
11312
11313        KeyListener mKeyListener;
11314        int mInputType = EditorInfo.TYPE_NULL;
11315
11316        boolean mDiscardNextActionUp;
11317        boolean mIgnoreActionUpEvent;
11318
11319        long mShowCursor;
11320        Blink mBlink;
11321
11322        boolean mCursorVisible = true;
11323        boolean mSelectAllOnFocus;
11324        boolean mTextIsSelectable;
11325
11326        CharSequence mError;
11327        boolean mErrorWasChanged;
11328        ErrorPopup mErrorPopup;
11329        /**
11330         * This flag is set if the TextView tries to display an error before it
11331         * is attached to the window (so its position is still unknown).
11332         * It causes the error to be shown later, when onAttachedToWindow()
11333         * is called.
11334         */
11335        boolean mShowErrorAfterAttach;
11336
11337        boolean mInBatchEditControllers;
11338
11339        SuggestionsPopupWindow mSuggestionsPopupWindow;
11340        SuggestionRangeSpan mSuggestionRangeSpan;
11341        Runnable mShowSuggestionRunnable;
11342
11343        final Drawable[] mCursorDrawable = new Drawable[2];
11344        int mCursorCount; // Current number of used mCursorDrawable: 0 (resource=0), 1 or 2 (split)
11345
11346        Drawable mSelectHandleLeft;
11347        Drawable mSelectHandleRight;
11348        Drawable mSelectHandleCenter;
11349
11350        // Global listener that detects changes in the global position of the TextView
11351        PositionListener mPositionListener;
11352
11353        float mLastDownPositionX, mLastDownPositionY;
11354        Callback mCustomSelectionActionModeCallback;
11355
11356        // Set when this TextView gained focus with some text selected. Will start selection mode.
11357        boolean mCreatedWithASelection;
11358
11359        WordIterator mWordIterator;
11360        SpellChecker mSpellChecker;
11361
11362        void onAttachedToWindow() {
11363            final ViewTreeObserver observer = getViewTreeObserver();
11364            // No need to create the controller.
11365            // The get method will add the listener on controller creation.
11366            if (mInsertionPointCursorController != null) {
11367                observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
11368            }
11369            if (mSelectionModifierCursorController != null) {
11370                observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
11371            }
11372            updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
11373        }
11374
11375        void onDetachedFromWindow() {
11376            if (mError != null) {
11377                hideError();
11378            }
11379
11380            if (mBlink != null) {
11381                mBlink.removeCallbacks(mBlink);
11382            }
11383
11384            if (mInsertionPointCursorController != null) {
11385                mInsertionPointCursorController.onDetached();
11386            }
11387
11388            if (mSelectionModifierCursorController != null) {
11389                mSelectionModifierCursorController.onDetached();
11390            }
11391
11392            if (mShowSuggestionRunnable != null) {
11393                removeCallbacks(mShowSuggestionRunnable);
11394            }
11395
11396            if (mTextDisplayList != null) {
11397                mTextDisplayList.invalidate();
11398            }
11399
11400            if (mSpellChecker != null) {
11401                mSpellChecker.closeSession();
11402                // Forces the creation of a new SpellChecker next time this window is created.
11403                // Will handle the cases where the settings has been changed in the meantime.
11404                mSpellChecker = null;
11405            }
11406
11407            hideControllers();
11408        }
11409
11410        void adjustInputType(boolean password, boolean passwordInputType,
11411                boolean webPasswordInputType, boolean numberPasswordInputType) {
11412            // mInputType has been set from inputType, possibly modified by mInputMethod.
11413            // Specialize mInputType to [web]password if we have a text class and the original input
11414            // type was a password.
11415            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
11416                if (password || passwordInputType) {
11417                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
11418                            | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
11419                }
11420                if (webPasswordInputType) {
11421                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
11422                            | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
11423                }
11424            } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
11425                if (numberPasswordInputType) {
11426                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
11427                            | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
11428                }
11429            }
11430        }
11431
11432        void setFrame() {
11433            if (mErrorPopup != null) {
11434                TextView tv = (TextView) mErrorPopup.getContentView();
11435                chooseSize(mErrorPopup, mError, tv);
11436                mErrorPopup.update(TextView.this, getErrorX(), getErrorY(),
11437                        mErrorPopup.getWidth(), mErrorPopup.getHeight());
11438            }
11439        }
11440
11441        void onFocusChanged(boolean focused, int direction) {
11442            mShowCursor = SystemClock.uptimeMillis();
11443            ensureEndedBatchEdit();
11444
11445            if (focused) {
11446                int selStart = getSelectionStart();
11447                int selEnd = getSelectionEnd();
11448
11449                // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
11450                // mode for these, unless there was a specific selection already started.
11451                final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
11452                        selEnd == mText.length();
11453
11454                mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
11455
11456                if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
11457                    // If a tap was used to give focus to that view, move cursor at tap position.
11458                    // Has to be done before onTakeFocus, which can be overloaded.
11459                    final int lastTapPosition = getLastTapPosition();
11460                    if (lastTapPosition >= 0) {
11461                        Selection.setSelection((Spannable) mText, lastTapPosition);
11462                    }
11463
11464                    // Note this may have to be moved out of the Editor class
11465                    if (mMovement != null) {
11466                        mMovement.onTakeFocus(TextView.this, (Spannable) mText, direction);
11467                    }
11468
11469                    // The DecorView does not have focus when the 'Done' ExtractEditText button is
11470                    // pressed. Since it is the ViewAncestor's mView, it requests focus before
11471                    // ExtractEditText clears focus, which gives focus to the ExtractEditText.
11472                    // This special case ensure that we keep current selection in that case.
11473                    // It would be better to know why the DecorView does not have focus at that time.
11474                    if (((TextView.this instanceof ExtractEditText) || mSelectionMoved) &&
11475                            selStart >= 0 && selEnd >= 0) {
11476                        /*
11477                         * Someone intentionally set the selection, so let them
11478                         * do whatever it is that they wanted to do instead of
11479                         * the default on-focus behavior.  We reset the selection
11480                         * here instead of just skipping the onTakeFocus() call
11481                         * because some movement methods do something other than
11482                         * just setting the selection in theirs and we still
11483                         * need to go through that path.
11484                         */
11485                        Selection.setSelection((Spannable) mText, selStart, selEnd);
11486                    }
11487
11488                    if (mSelectAllOnFocus) {
11489                        selectAll();
11490                    }
11491
11492                    mTouchFocusSelected = true;
11493                }
11494
11495                mFrozenWithFocus = false;
11496                mSelectionMoved = false;
11497
11498                if (mError != null) {
11499                    showError();
11500                }
11501
11502                makeBlink();
11503            } else {
11504                if (mError != null) {
11505                    hideError();
11506                }
11507                // Don't leave us in the middle of a batch edit.
11508                onEndBatchEdit();
11509
11510                if (TextView.this instanceof ExtractEditText) {
11511                    // terminateTextSelectionMode removes selection, which we want to keep when
11512                    // ExtractEditText goes out of focus.
11513                    final int selStart = getSelectionStart();
11514                    final int selEnd = getSelectionEnd();
11515                    hideControllers();
11516                    Selection.setSelection((Spannable) mText, selStart, selEnd);
11517                } else {
11518                    hideControllers();
11519                    downgradeEasyCorrectionSpans();
11520                }
11521
11522                // No need to create the controller
11523                if (mSelectionModifierCursorController != null) {
11524                    mSelectionModifierCursorController.resetTouchOffsets();
11525                }
11526            }
11527        }
11528
11529        void sendOnTextChanged(int start, int after) {
11530            updateSpellCheckSpans(start, start + after, false);
11531            mTextDisplayListIsValid = false;
11532
11533            // Hide the controllers as soon as text is modified (typing, procedural...)
11534            // We do not hide the span controllers, since they can be added when a new text is
11535            // inserted into the text view (voice IME).
11536            hideCursorControllers();
11537        }
11538
11539        private int getLastTapPosition() {
11540            // No need to create the controller at that point, no last tap position saved
11541            if (mSelectionModifierCursorController != null) {
11542                int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
11543                if (lastTapPosition >= 0) {
11544                    // Safety check, should not be possible.
11545                    if (lastTapPosition > mText.length()) {
11546                        Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
11547                                + mText.length() + ")");
11548                        lastTapPosition = mText.length();
11549                    }
11550                    return lastTapPosition;
11551                }
11552            }
11553
11554            return -1;
11555        }
11556
11557        void onWindowFocusChanged(boolean hasWindowFocus) {
11558            if (hasWindowFocus) {
11559                if (mBlink != null) {
11560                    mBlink.uncancel();
11561                    makeBlink();
11562                }
11563            } else {
11564                if (mBlink != null) {
11565                    mBlink.cancel();
11566                }
11567                if (mInputContentType != null) {
11568                    mInputContentType.enterDown = false;
11569                }
11570                // Order matters! Must be done before onParentLostFocus to rely on isShowingUp
11571                hideControllers();
11572                if (mSuggestionsPopupWindow != null) {
11573                    mSuggestionsPopupWindow.onParentLostFocus();
11574                }
11575
11576                // Don't leave us in the middle of a batch edit.
11577                onEndBatchEdit();
11578            }
11579        }
11580
11581        void onTouchEvent(MotionEvent event) {
11582            if (hasSelectionController()) {
11583                getSelectionController().onTouchEvent(event);
11584            }
11585
11586            if (mShowSuggestionRunnable != null) {
11587                removeCallbacks(mShowSuggestionRunnable);
11588                mShowSuggestionRunnable = null;
11589            }
11590
11591            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
11592                mLastDownPositionX = event.getX();
11593                mLastDownPositionY = event.getY();
11594
11595                // Reset this state; it will be re-set if super.onTouchEvent
11596                // causes focus to move to the view.
11597                mTouchFocusSelected = false;
11598                mIgnoreActionUpEvent = false;
11599            }
11600        }
11601
11602        void onDraw(Canvas canvas, Layout layout, Path highlight, int cursorOffsetVertical) {
11603            final int selectionStart = getSelectionStart();
11604            final int selectionEnd = getSelectionEnd();
11605
11606            final InputMethodState ims = mInputMethodState;
11607            if (ims != null && ims.mBatchEditNesting == 0) {
11608                InputMethodManager imm = InputMethodManager.peekInstance();
11609                if (imm != null) {
11610                    if (imm.isActive(TextView.this)) {
11611                        boolean reported = false;
11612                        if (ims.mContentChanged || ims.mSelectionModeChanged) {
11613                            // We are in extract mode and the content has changed
11614                            // in some way... just report complete new text to the
11615                            // input method.
11616                            reported = reportExtractedText();
11617                        }
11618                        if (!reported && highlight != null) {
11619                            int candStart = -1;
11620                            int candEnd = -1;
11621                            if (mText instanceof Spannable) {
11622                                Spannable sp = (Spannable)mText;
11623                                candStart = EditableInputConnection.getComposingSpanStart(sp);
11624                                candEnd = EditableInputConnection.getComposingSpanEnd(sp);
11625                            }
11626                            imm.updateSelection(TextView.this,
11627                                    selectionStart, selectionEnd, candStart, candEnd);
11628                        }
11629                    }
11630
11631                    if (imm.isWatchingCursor(TextView.this) && highlight != null) {
11632                        highlight.computeBounds(ims.mTmpRectF, true);
11633                        ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
11634
11635                        canvas.getMatrix().mapPoints(ims.mTmpOffset);
11636                        ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
11637
11638                        ims.mTmpRectF.offset(0, cursorOffsetVertical);
11639
11640                        ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
11641                                (int)(ims.mTmpRectF.top + 0.5),
11642                                (int)(ims.mTmpRectF.right + 0.5),
11643                                (int)(ims.mTmpRectF.bottom + 0.5));
11644
11645                        imm.updateCursor(TextView.this,
11646                                ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
11647                                ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
11648                    }
11649                }
11650            }
11651
11652            if (mCorrectionHighlighter != null) {
11653                mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
11654            }
11655
11656            if (highlight != null && selectionStart == selectionEnd && mCursorCount > 0) {
11657                drawCursor(canvas, cursorOffsetVertical);
11658                // Rely on the drawable entirely, do not draw the cursor line.
11659                // Has to be done after the IMM related code above which relies on the highlight.
11660                highlight = null;
11661            }
11662
11663            if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
11664                final int width = mRight - mLeft;
11665                final int height = mBottom - mTop;
11666
11667                if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
11668                        !mTextDisplayListIsValid) {
11669                    if (mTextDisplayList == null) {
11670                        mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
11671                    }
11672
11673                    final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
11674                    try {
11675                        hardwareCanvas.setViewport(width, height);
11676                        // The dirty rect should always be null for a display list
11677                        hardwareCanvas.onPreDraw(null);
11678                        hardwareCanvas.translate(-mScrollX, -mScrollY);
11679                        layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
11680                        hardwareCanvas.translate(mScrollX, mScrollY);
11681                    } finally {
11682                        hardwareCanvas.onPostDraw();
11683                        mTextDisplayList.end();
11684                        mTextDisplayListIsValid = true;
11685                    }
11686                }
11687                canvas.translate(mScrollX, mScrollY);
11688                ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
11689                        DisplayList.FLAG_CLIP_CHILDREN);
11690                canvas.translate(-mScrollX, -mScrollY);
11691            } else {
11692                layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
11693            }
11694
11695            if (mMarquee != null && mMarquee.shouldDrawGhost()) {
11696                canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
11697                layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
11698            }
11699        }
11700
11701        private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
11702            final boolean translate = cursorOffsetVertical != 0;
11703            if (translate) canvas.translate(0, cursorOffsetVertical);
11704            for (int i = 0; i < getEditor().mCursorCount; i++) {
11705                mCursorDrawable[i].draw(canvas);
11706            }
11707            if (translate) canvas.translate(0, -cursorOffsetVertical);
11708        }
11709
11710        private void updateCursorsPositions() {
11711            if (mCursorDrawableRes == 0) {
11712                mCursorCount = 0;
11713                return;
11714            }
11715
11716            final int offset = getSelectionStart();
11717            final int line = mLayout.getLineForOffset(offset);
11718            final int top = mLayout.getLineTop(line);
11719            final int bottom = mLayout.getLineTop(line + 1);
11720
11721            mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
11722
11723            int middle = bottom;
11724            if (mCursorCount == 2) {
11725                // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
11726                middle = (top + bottom) >> 1;
11727            }
11728
11729            updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
11730
11731            if (mCursorCount == 2) {
11732                updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
11733            }
11734        }
11735
11736        private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
11737            if (mCursorDrawable[cursorIndex] == null)
11738                mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
11739
11740            if (mTempRect == null) mTempRect = new Rect();
11741            mCursorDrawable[cursorIndex].getPadding(mTempRect);
11742            final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
11743            horizontal = Math.max(0.5f, horizontal - 0.5f);
11744            final int left = (int) (horizontal) - mTempRect.left;
11745            mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
11746                    bottom + mTempRect.bottom);
11747        }
11748    }
11749}
11750