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