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