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