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