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