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