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