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