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