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