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