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