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