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