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