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