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