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