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