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