TextView.java revision 22a167cac8f585ffd3ca73e40b82a26c1e09df11
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            final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
7960                    wantWidth)
7961                    .setDisplayText(mTransformed)
7962                    .setAlignment(alignment)
7963                    .setTextDirection(mTextDir)
7964                    .setLineSpacing(mSpacingAdd, mSpacingMult)
7965                    .setIncludePad(mIncludePad)
7966                    .setBreakStrategy(mBreakStrategy)
7967                    .setHyphenationFrequency(mHyphenationFrequency)
7968                    .setJustificationMode(mJustificationMode)
7969                    .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
7970                    .setEllipsizedWidth(ellipsisWidth);
7971            result = builder.build();
7972        } else {
7973            if (boring == UNKNOWN_BORING) {
7974                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7975                if (boring != null) {
7976                    mBoring = boring;
7977                }
7978            }
7979
7980            if (boring != null) {
7981                if (boring.width <= wantWidth
7982                        && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
7983                    if (useSaved && mSavedLayout != null) {
7984                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7985                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
7986                                boring, mIncludePad);
7987                    } else {
7988                        result = BoringLayout.make(mTransformed, mTextPaint,
7989                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
7990                                boring, mIncludePad);
7991                    }
7992
7993                    if (useSaved) {
7994                        mSavedLayout = (BoringLayout) result;
7995                    }
7996                } else if (shouldEllipsize && boring.width <= wantWidth) {
7997                    if (useSaved && mSavedLayout != null) {
7998                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7999                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
8000                                boring, mIncludePad, effectiveEllipsize,
8001                                ellipsisWidth);
8002                    } else {
8003                        result = BoringLayout.make(mTransformed, mTextPaint,
8004                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
8005                                boring, mIncludePad, effectiveEllipsize,
8006                                ellipsisWidth);
8007                    }
8008                }
8009            }
8010        }
8011        if (result == null) {
8012            StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
8013                    0, mTransformed.length(), mTextPaint, wantWidth)
8014                    .setAlignment(alignment)
8015                    .setTextDirection(mTextDir)
8016                    .setLineSpacing(mSpacingAdd, mSpacingMult)
8017                    .setIncludePad(mIncludePad)
8018                    .setBreakStrategy(mBreakStrategy)
8019                    .setHyphenationFrequency(mHyphenationFrequency)
8020                    .setJustificationMode(mJustificationMode)
8021                    .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8022            if (shouldEllipsize) {
8023                builder.setEllipsize(effectiveEllipsize)
8024                        .setEllipsizedWidth(ellipsisWidth);
8025            }
8026            result = builder.build();
8027        }
8028        return result;
8029    }
8030
8031    private boolean compressText(float width) {
8032        if (isHardwareAccelerated()) return false;
8033
8034        // Only compress the text if it hasn't been compressed by the previous pass
8035        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
8036                && mTextPaint.getTextScaleX() == 1.0f) {
8037            final float textWidth = mLayout.getLineWidth(0);
8038            final float overflow = (textWidth + 1.0f - width) / width;
8039            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
8040                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
8041                post(new Runnable() {
8042                    public void run() {
8043                        requestLayout();
8044                    }
8045                });
8046                return true;
8047            }
8048        }
8049
8050        return false;
8051    }
8052
8053    private static int desired(Layout layout) {
8054        int n = layout.getLineCount();
8055        CharSequence text = layout.getText();
8056        float max = 0;
8057
8058        // if any line was wrapped, we can't use it.
8059        // but it's ok for the last line not to have a newline
8060
8061        for (int i = 0; i < n - 1; i++) {
8062            if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
8063                return -1;
8064            }
8065        }
8066
8067        for (int i = 0; i < n; i++) {
8068            max = Math.max(max, layout.getLineWidth(i));
8069        }
8070
8071        return (int) Math.ceil(max);
8072    }
8073
8074    /**
8075     * Set whether the TextView includes extra top and bottom padding to make
8076     * room for accents that go above the normal ascent and descent.
8077     * The default is true.
8078     *
8079     * @see #getIncludeFontPadding()
8080     *
8081     * @attr ref android.R.styleable#TextView_includeFontPadding
8082     */
8083    public void setIncludeFontPadding(boolean includepad) {
8084        if (mIncludePad != includepad) {
8085            mIncludePad = includepad;
8086
8087            if (mLayout != null) {
8088                nullLayouts();
8089                requestLayout();
8090                invalidate();
8091            }
8092        }
8093    }
8094
8095    /**
8096     * Gets whether the TextView includes extra top and bottom padding to make
8097     * room for accents that go above the normal ascent and descent.
8098     *
8099     * @see #setIncludeFontPadding(boolean)
8100     *
8101     * @attr ref android.R.styleable#TextView_includeFontPadding
8102     */
8103    public boolean getIncludeFontPadding() {
8104        return mIncludePad;
8105    }
8106
8107    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
8108
8109    @Override
8110    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
8111        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
8112        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
8113        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
8114        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
8115
8116        int width;
8117        int height;
8118
8119        BoringLayout.Metrics boring = UNKNOWN_BORING;
8120        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
8121
8122        if (mTextDir == null) {
8123            mTextDir = getTextDirectionHeuristic();
8124        }
8125
8126        int des = -1;
8127        boolean fromexisting = false;
8128        final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
8129                ?  (float) widthSize : Float.MAX_VALUE;
8130
8131        if (widthMode == MeasureSpec.EXACTLY) {
8132            // Parent has told us how big to be. So be it.
8133            width = widthSize;
8134        } else {
8135            if (mLayout != null && mEllipsize == null) {
8136                des = desired(mLayout);
8137            }
8138
8139            if (des < 0) {
8140                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8141                if (boring != null) {
8142                    mBoring = boring;
8143                }
8144            } else {
8145                fromexisting = true;
8146            }
8147
8148            if (boring == null || boring == UNKNOWN_BORING) {
8149                if (des < 0) {
8150                    des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
8151                            mTransformed.length(), mTextPaint, mTextDir, widthLimit));
8152                }
8153                width = des;
8154            } else {
8155                width = boring.width;
8156            }
8157
8158            final Drawables dr = mDrawables;
8159            if (dr != null) {
8160                width = Math.max(width, dr.mDrawableWidthTop);
8161                width = Math.max(width, dr.mDrawableWidthBottom);
8162            }
8163
8164            if (mHint != null) {
8165                int hintDes = -1;
8166                int hintWidth;
8167
8168                if (mHintLayout != null && mEllipsize == null) {
8169                    hintDes = desired(mHintLayout);
8170                }
8171
8172                if (hintDes < 0) {
8173                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
8174                    if (hintBoring != null) {
8175                        mHintBoring = hintBoring;
8176                    }
8177                }
8178
8179                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
8180                    if (hintDes < 0) {
8181                        hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
8182                                mHint.length(), mTextPaint, mTextDir, widthLimit));
8183                    }
8184                    hintWidth = hintDes;
8185                } else {
8186                    hintWidth = hintBoring.width;
8187                }
8188
8189                if (hintWidth > width) {
8190                    width = hintWidth;
8191                }
8192            }
8193
8194            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
8195
8196            if (mMaxWidthMode == EMS) {
8197                width = Math.min(width, mMaxWidth * getLineHeight());
8198            } else {
8199                width = Math.min(width, mMaxWidth);
8200            }
8201
8202            if (mMinWidthMode == EMS) {
8203                width = Math.max(width, mMinWidth * getLineHeight());
8204            } else {
8205                width = Math.max(width, mMinWidth);
8206            }
8207
8208            // Check against our minimum width
8209            width = Math.max(width, getSuggestedMinimumWidth());
8210
8211            if (widthMode == MeasureSpec.AT_MOST) {
8212                width = Math.min(widthSize, width);
8213            }
8214        }
8215
8216        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
8217        int unpaddedWidth = want;
8218
8219        if (mHorizontallyScrolling) want = VERY_WIDE;
8220
8221        int hintWant = want;
8222        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
8223
8224        if (mLayout == null) {
8225            makeNewLayout(want, hintWant, boring, hintBoring,
8226                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8227        } else {
8228            final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
8229                    || (mLayout.getEllipsizedWidth()
8230                            != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
8231
8232            final boolean widthChanged = (mHint == null) && (mEllipsize == null)
8233                    && (want > mLayout.getWidth())
8234                    && (mLayout instanceof BoringLayout
8235                            || (fromexisting && des >= 0 && des <= want));
8236
8237            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
8238
8239            if (layoutChanged || maximumChanged) {
8240                if (!maximumChanged && widthChanged) {
8241                    mLayout.increaseWidthTo(want);
8242                } else {
8243                    makeNewLayout(want, hintWant, boring, hintBoring,
8244                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8245                }
8246            } else {
8247                // Nothing has changed
8248            }
8249        }
8250
8251        if (heightMode == MeasureSpec.EXACTLY) {
8252            // Parent has told us how big to be. So be it.
8253            height = heightSize;
8254            mDesiredHeightAtMeasure = -1;
8255        } else {
8256            int desired = getDesiredHeight();
8257
8258            height = desired;
8259            mDesiredHeightAtMeasure = desired;
8260
8261            if (heightMode == MeasureSpec.AT_MOST) {
8262                height = Math.min(desired, heightSize);
8263            }
8264        }
8265
8266        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
8267        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
8268            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
8269        }
8270
8271        /*
8272         * We didn't let makeNewLayout() register to bring the cursor into view,
8273         * so do it here if there is any possibility that it is needed.
8274         */
8275        if (mMovement != null
8276                || mLayout.getWidth() > unpaddedWidth
8277                || mLayout.getHeight() > unpaddedHeight) {
8278            registerForPreDraw();
8279        } else {
8280            scrollTo(0, 0);
8281        }
8282
8283        setMeasuredDimension(width, height);
8284    }
8285
8286    /**
8287     * Automatically computes and sets the text size.
8288     */
8289    private void autoSizeText() {
8290        if (!isAutoSizeEnabled()) {
8291            return;
8292        }
8293
8294        if (mNeedsAutoSizeText) {
8295            if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
8296                return;
8297            }
8298
8299            final int availableWidth = mHorizontallyScrolling
8300                    ? VERY_WIDE
8301                    : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
8302            final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
8303                    - getExtendedPaddingTop();
8304
8305            if (availableWidth <= 0 || availableHeight <= 0) {
8306                return;
8307            }
8308
8309            synchronized (TEMP_RECTF) {
8310                TEMP_RECTF.setEmpty();
8311                TEMP_RECTF.right = availableWidth;
8312                TEMP_RECTF.bottom = availableHeight;
8313                final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
8314
8315                if (optimalTextSize != getTextSize()) {
8316                    setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
8317                            false /* shouldRequestLayout */);
8318
8319                    makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
8320                            mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8321                            false /* bringIntoView */);
8322                }
8323            }
8324        }
8325        // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
8326        // after the next layout pass should set this to false.
8327        mNeedsAutoSizeText = true;
8328    }
8329
8330    /**
8331     * Performs a binary search to find the largest text size that will still fit within the size
8332     * available to this view.
8333     */
8334    private int findLargestTextSizeWhichFits(RectF availableSpace) {
8335        final int sizesCount = mAutoSizeTextSizesInPx.length;
8336        if (sizesCount == 0) {
8337            throw new IllegalStateException("No available text sizes to choose from.");
8338        }
8339
8340        int bestSizeIndex = 0;
8341        int lowIndex = bestSizeIndex + 1;
8342        int highIndex = sizesCount - 1;
8343        int sizeToTryIndex;
8344        while (lowIndex <= highIndex) {
8345            sizeToTryIndex = (lowIndex + highIndex) / 2;
8346            if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
8347                bestSizeIndex = lowIndex;
8348                lowIndex = sizeToTryIndex + 1;
8349            } else {
8350                highIndex = sizeToTryIndex - 1;
8351                bestSizeIndex = highIndex;
8352            }
8353        }
8354
8355        return mAutoSizeTextSizesInPx[bestSizeIndex];
8356    }
8357
8358    private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
8359        final CharSequence text = mTransformed != null
8360                ? mTransformed
8361                : getText();
8362        final int maxLines = getMaxLines();
8363        if (mTempTextPaint == null) {
8364            mTempTextPaint = new TextPaint();
8365        } else {
8366            mTempTextPaint.reset();
8367        }
8368        mTempTextPaint.set(getPaint());
8369        mTempTextPaint.setTextSize(suggestedSizeInPx);
8370
8371        final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
8372                text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
8373
8374        layoutBuilder.setAlignment(getLayoutAlignment())
8375                .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
8376                .setIncludePad(getIncludeFontPadding())
8377                .setBreakStrategy(getBreakStrategy())
8378                .setHyphenationFrequency(getHyphenationFrequency())
8379                .setJustificationMode(getJustificationMode())
8380                .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
8381                .setTextDirection(getTextDirectionHeuristic());
8382
8383        final StaticLayout layout = layoutBuilder.build();
8384
8385        // Lines overflow.
8386        if (maxLines != -1 && layout.getLineCount() > maxLines) {
8387            return false;
8388        }
8389
8390        // Height overflow.
8391        if (layout.getHeight() > availableSpace.bottom) {
8392            return false;
8393        }
8394
8395        return true;
8396    }
8397
8398    private int getDesiredHeight() {
8399        return Math.max(
8400                getDesiredHeight(mLayout, true),
8401                getDesiredHeight(mHintLayout, mEllipsize != null));
8402    }
8403
8404    private int getDesiredHeight(Layout layout, boolean cap) {
8405        if (layout == null) {
8406            return 0;
8407        }
8408
8409        /*
8410        * Don't cap the hint to a certain number of lines.
8411        * (Do cap it, though, if we have a maximum pixel height.)
8412        */
8413        int desired = layout.getHeight(cap);
8414
8415        final Drawables dr = mDrawables;
8416        if (dr != null) {
8417            desired = Math.max(desired, dr.mDrawableHeightLeft);
8418            desired = Math.max(desired, dr.mDrawableHeightRight);
8419        }
8420
8421        int linecount = layout.getLineCount();
8422        final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
8423        desired += padding;
8424
8425        if (mMaxMode != LINES) {
8426            desired = Math.min(desired, mMaximum);
8427        } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) {
8428            desired = layout.getLineTop(mMaximum);
8429
8430            if (dr != null) {
8431                desired = Math.max(desired, dr.mDrawableHeightLeft);
8432                desired = Math.max(desired, dr.mDrawableHeightRight);
8433            }
8434
8435            desired += padding;
8436            linecount = mMaximum;
8437        }
8438
8439        if (mMinMode == LINES) {
8440            if (linecount < mMinimum) {
8441                desired += getLineHeight() * (mMinimum - linecount);
8442            }
8443        } else {
8444            desired = Math.max(desired, mMinimum);
8445        }
8446
8447        // Check against our minimum height
8448        desired = Math.max(desired, getSuggestedMinimumHeight());
8449
8450        return desired;
8451    }
8452
8453    /**
8454     * Check whether a change to the existing text layout requires a
8455     * new view layout.
8456     */
8457    private void checkForResize() {
8458        boolean sizeChanged = false;
8459
8460        if (mLayout != null) {
8461            // Check if our width changed
8462            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
8463                sizeChanged = true;
8464                invalidate();
8465            }
8466
8467            // Check if our height changed
8468            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
8469                int desiredHeight = getDesiredHeight();
8470
8471                if (desiredHeight != this.getHeight()) {
8472                    sizeChanged = true;
8473                }
8474            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
8475                if (mDesiredHeightAtMeasure >= 0) {
8476                    int desiredHeight = getDesiredHeight();
8477
8478                    if (desiredHeight != mDesiredHeightAtMeasure) {
8479                        sizeChanged = true;
8480                    }
8481                }
8482            }
8483        }
8484
8485        if (sizeChanged) {
8486            requestLayout();
8487            // caller will have already invalidated
8488        }
8489    }
8490
8491    /**
8492     * Check whether entirely new text requires a new view layout
8493     * or merely a new text layout.
8494     */
8495    private void checkForRelayout() {
8496        // If we have a fixed width, we can just swap in a new text layout
8497        // if the text height stays the same or if the view height is fixed.
8498
8499        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
8500                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
8501                && (mHint == null || mHintLayout != null)
8502                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
8503            // Static width, so try making a new text layout.
8504
8505            int oldht = mLayout.getHeight();
8506            int want = mLayout.getWidth();
8507            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
8508
8509            /*
8510             * No need to bring the text into view, since the size is not
8511             * changing (unless we do the requestLayout(), in which case it
8512             * will happen at measure).
8513             */
8514            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
8515                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8516                          false);
8517
8518            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
8519                // In a fixed-height view, so use our new text layout.
8520                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
8521                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
8522                    autoSizeText();
8523                    invalidate();
8524                    return;
8525                }
8526
8527                // Dynamic height, but height has stayed the same,
8528                // so use our new text layout.
8529                if (mLayout.getHeight() == oldht
8530                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
8531                    autoSizeText();
8532                    invalidate();
8533                    return;
8534                }
8535            }
8536
8537            // We lose: the height has changed and we have a dynamic height.
8538            // Request a new view layout using our new text layout.
8539            requestLayout();
8540            invalidate();
8541        } else {
8542            // Dynamic width, so we have no choice but to request a new
8543            // view layout with a new text layout.
8544            nullLayouts();
8545            requestLayout();
8546            invalidate();
8547        }
8548    }
8549
8550    @Override
8551    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8552        super.onLayout(changed, left, top, right, bottom);
8553        if (mDeferScroll >= 0) {
8554            int curs = mDeferScroll;
8555            mDeferScroll = -1;
8556            bringPointIntoView(Math.min(curs, mText.length()));
8557        }
8558        // Call auto-size after the width and height have been calculated.
8559        autoSizeText();
8560    }
8561
8562    private boolean isShowingHint() {
8563        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
8564    }
8565
8566    /**
8567     * Returns true if anything changed.
8568     */
8569    private boolean bringTextIntoView() {
8570        Layout layout = isShowingHint() ? mHintLayout : mLayout;
8571        int line = 0;
8572        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8573            line = layout.getLineCount() - 1;
8574        }
8575
8576        Layout.Alignment a = layout.getParagraphAlignment(line);
8577        int dir = layout.getParagraphDirection(line);
8578        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8579        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8580        int ht = layout.getHeight();
8581
8582        int scrollx, scrolly;
8583
8584        // Convert to left, center, or right alignment.
8585        if (a == Layout.Alignment.ALIGN_NORMAL) {
8586            a = dir == Layout.DIR_LEFT_TO_RIGHT
8587                    ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8588        } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
8589            a = dir == Layout.DIR_LEFT_TO_RIGHT
8590                    ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8591        }
8592
8593        if (a == Layout.Alignment.ALIGN_CENTER) {
8594            /*
8595             * Keep centered if possible, or, if it is too wide to fit,
8596             * keep leading edge in view.
8597             */
8598
8599            int left = (int) Math.floor(layout.getLineLeft(line));
8600            int right = (int) Math.ceil(layout.getLineRight(line));
8601
8602            if (right - left < hspace) {
8603                scrollx = (right + left) / 2 - hspace / 2;
8604            } else {
8605                if (dir < 0) {
8606                    scrollx = right - hspace;
8607                } else {
8608                    scrollx = left;
8609                }
8610            }
8611        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
8612            int right = (int) Math.ceil(layout.getLineRight(line));
8613            scrollx = right - hspace;
8614        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
8615            scrollx = (int) Math.floor(layout.getLineLeft(line));
8616        }
8617
8618        if (ht < vspace) {
8619            scrolly = 0;
8620        } else {
8621            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8622                scrolly = ht - vspace;
8623            } else {
8624                scrolly = 0;
8625            }
8626        }
8627
8628        if (scrollx != mScrollX || scrolly != mScrollY) {
8629            scrollTo(scrollx, scrolly);
8630            return true;
8631        } else {
8632            return false;
8633        }
8634    }
8635
8636    /**
8637     * Move the point, specified by the offset, into the view if it is needed.
8638     * This has to be called after layout. Returns true if anything changed.
8639     */
8640    public boolean bringPointIntoView(int offset) {
8641        if (isLayoutRequested()) {
8642            mDeferScroll = offset;
8643            return false;
8644        }
8645        boolean changed = false;
8646
8647        Layout layout = isShowingHint() ? mHintLayout : mLayout;
8648
8649        if (layout == null) return changed;
8650
8651        int line = layout.getLineForOffset(offset);
8652
8653        int grav;
8654
8655        switch (layout.getParagraphAlignment(line)) {
8656            case ALIGN_LEFT:
8657                grav = 1;
8658                break;
8659            case ALIGN_RIGHT:
8660                grav = -1;
8661                break;
8662            case ALIGN_NORMAL:
8663                grav = layout.getParagraphDirection(line);
8664                break;
8665            case ALIGN_OPPOSITE:
8666                grav = -layout.getParagraphDirection(line);
8667                break;
8668            case ALIGN_CENTER:
8669            default:
8670                grav = 0;
8671                break;
8672        }
8673
8674        // We only want to clamp the cursor to fit within the layout width
8675        // in left-to-right modes, because in a right to left alignment,
8676        // we want to scroll to keep the line-right on the screen, as other
8677        // lines are likely to have text flush with the right margin, which
8678        // we want to keep visible.
8679        // A better long-term solution would probably be to measure both
8680        // the full line and a blank-trimmed version, and, for example, use
8681        // the latter measurement for centering and right alignment, but for
8682        // the time being we only implement the cursor clamping in left to
8683        // right where it is most likely to be annoying.
8684        final boolean clamped = grav > 0;
8685        // FIXME: Is it okay to truncate this, or should we round?
8686        final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
8687        final int top = layout.getLineTop(line);
8688        final int bottom = layout.getLineTop(line + 1);
8689
8690        int left = (int) Math.floor(layout.getLineLeft(line));
8691        int right = (int) Math.ceil(layout.getLineRight(line));
8692        int ht = layout.getHeight();
8693
8694        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8695        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8696        if (!mHorizontallyScrolling && right - left > hspace && right > x) {
8697            // If cursor has been clamped, make sure we don't scroll.
8698            right = Math.max(x, left + hspace);
8699        }
8700
8701        int hslack = (bottom - top) / 2;
8702        int vslack = hslack;
8703
8704        if (vslack > vspace / 4) {
8705            vslack = vspace / 4;
8706        }
8707        if (hslack > hspace / 4) {
8708            hslack = hspace / 4;
8709        }
8710
8711        int hs = mScrollX;
8712        int vs = mScrollY;
8713
8714        if (top - vs < vslack) {
8715            vs = top - vslack;
8716        }
8717        if (bottom - vs > vspace - vslack) {
8718            vs = bottom - (vspace - vslack);
8719        }
8720        if (ht - vs < vspace) {
8721            vs = ht - vspace;
8722        }
8723        if (0 - vs > 0) {
8724            vs = 0;
8725        }
8726
8727        if (grav != 0) {
8728            if (x - hs < hslack) {
8729                hs = x - hslack;
8730            }
8731            if (x - hs > hspace - hslack) {
8732                hs = x - (hspace - hslack);
8733            }
8734        }
8735
8736        if (grav < 0) {
8737            if (left - hs > 0) {
8738                hs = left;
8739            }
8740            if (right - hs < hspace) {
8741                hs = right - hspace;
8742            }
8743        } else if (grav > 0) {
8744            if (right - hs < hspace) {
8745                hs = right - hspace;
8746            }
8747            if (left - hs > 0) {
8748                hs = left;
8749            }
8750        } else /* grav == 0 */ {
8751            if (right - left <= hspace) {
8752                /*
8753                 * If the entire text fits, center it exactly.
8754                 */
8755                hs = left - (hspace - (right - left)) / 2;
8756            } else if (x > right - hslack) {
8757                /*
8758                 * If we are near the right edge, keep the right edge
8759                 * at the edge of the view.
8760                 */
8761                hs = right - hspace;
8762            } else if (x < left + hslack) {
8763                /*
8764                 * If we are near the left edge, keep the left edge
8765                 * at the edge of the view.
8766                 */
8767                hs = left;
8768            } else if (left > hs) {
8769                /*
8770                 * Is there whitespace visible at the left?  Fix it if so.
8771                 */
8772                hs = left;
8773            } else if (right < hs + hspace) {
8774                /*
8775                 * Is there whitespace visible at the right?  Fix it if so.
8776                 */
8777                hs = right - hspace;
8778            } else {
8779                /*
8780                 * Otherwise, float as needed.
8781                 */
8782                if (x - hs < hslack) {
8783                    hs = x - hslack;
8784                }
8785                if (x - hs > hspace - hslack) {
8786                    hs = x - (hspace - hslack);
8787                }
8788            }
8789        }
8790
8791        if (hs != mScrollX || vs != mScrollY) {
8792            if (mScroller == null) {
8793                scrollTo(hs, vs);
8794            } else {
8795                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
8796                int dx = hs - mScrollX;
8797                int dy = vs - mScrollY;
8798
8799                if (duration > ANIMATED_SCROLL_GAP) {
8800                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
8801                    awakenScrollBars(mScroller.getDuration());
8802                    invalidate();
8803                } else {
8804                    if (!mScroller.isFinished()) {
8805                        mScroller.abortAnimation();
8806                    }
8807
8808                    scrollBy(dx, dy);
8809                }
8810
8811                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
8812            }
8813
8814            changed = true;
8815        }
8816
8817        if (isFocused()) {
8818            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
8819            // requestRectangleOnScreen() is in terms of content coordinates.
8820
8821            // The offsets here are to ensure the rectangle we are using is
8822            // within our view bounds, in case the cursor is on the far left
8823            // or right.  If it isn't withing the bounds, then this request
8824            // will be ignored.
8825            if (mTempRect == null) mTempRect = new Rect();
8826            mTempRect.set(x - 2, top, x + 2, bottom);
8827            getInterestingRect(mTempRect, line);
8828            mTempRect.offset(mScrollX, mScrollY);
8829
8830            if (requestRectangleOnScreen(mTempRect)) {
8831                changed = true;
8832            }
8833        }
8834
8835        return changed;
8836    }
8837
8838    /**
8839     * Move the cursor, if needed, so that it is at an offset that is visible
8840     * to the user.  This will not move the cursor if it represents more than
8841     * one character (a selection range).  This will only work if the
8842     * TextView contains spannable text; otherwise it will do nothing.
8843     *
8844     * @return True if the cursor was actually moved, false otherwise.
8845     */
8846    public boolean moveCursorToVisibleOffset() {
8847        if (!(mText instanceof Spannable)) {
8848            return false;
8849        }
8850        int start = getSelectionStart();
8851        int end = getSelectionEnd();
8852        if (start != end) {
8853            return false;
8854        }
8855
8856        // First: make sure the line is visible on screen:
8857
8858        int line = mLayout.getLineForOffset(start);
8859
8860        final int top = mLayout.getLineTop(line);
8861        final int bottom = mLayout.getLineTop(line + 1);
8862        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8863        int vslack = (bottom - top) / 2;
8864        if (vslack > vspace / 4) {
8865            vslack = vspace / 4;
8866        }
8867        final int vs = mScrollY;
8868
8869        if (top < (vs + vslack)) {
8870            line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
8871        } else if (bottom > (vspace + vs - vslack)) {
8872            line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
8873        }
8874
8875        // Next: make sure the character is visible on screen:
8876
8877        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8878        final int hs = mScrollX;
8879        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
8880        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
8881
8882        // line might contain bidirectional text
8883        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
8884        final int highChar = leftChar > rightChar ? leftChar : rightChar;
8885
8886        int newStart = start;
8887        if (newStart < lowChar) {
8888            newStart = lowChar;
8889        } else if (newStart > highChar) {
8890            newStart = highChar;
8891        }
8892
8893        if (newStart != start) {
8894            Selection.setSelection((Spannable) mText, newStart);
8895            return true;
8896        }
8897
8898        return false;
8899    }
8900
8901    @Override
8902    public void computeScroll() {
8903        if (mScroller != null) {
8904            if (mScroller.computeScrollOffset()) {
8905                mScrollX = mScroller.getCurrX();
8906                mScrollY = mScroller.getCurrY();
8907                invalidateParentCaches();
8908                postInvalidate();  // So we draw again
8909            }
8910        }
8911    }
8912
8913    private void getInterestingRect(Rect r, int line) {
8914        convertFromViewportToContentCoordinates(r);
8915
8916        // Rectangle can can be expanded on first and last line to take
8917        // padding into account.
8918        // TODO Take left/right padding into account too?
8919        if (line == 0) r.top -= getExtendedPaddingTop();
8920        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
8921    }
8922
8923    private void convertFromViewportToContentCoordinates(Rect r) {
8924        final int horizontalOffset = viewportToContentHorizontalOffset();
8925        r.left += horizontalOffset;
8926        r.right += horizontalOffset;
8927
8928        final int verticalOffset = viewportToContentVerticalOffset();
8929        r.top += verticalOffset;
8930        r.bottom += verticalOffset;
8931    }
8932
8933    int viewportToContentHorizontalOffset() {
8934        return getCompoundPaddingLeft() - mScrollX;
8935    }
8936
8937    int viewportToContentVerticalOffset() {
8938        int offset = getExtendedPaddingTop() - mScrollY;
8939        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8940            offset += getVerticalOffset(false);
8941        }
8942        return offset;
8943    }
8944
8945    @Override
8946    public void debug(int depth) {
8947        super.debug(depth);
8948
8949        String output = debugIndent(depth);
8950        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
8951                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
8952                + "} ";
8953
8954        if (mText != null) {
8955
8956            output += "mText=\"" + mText + "\" ";
8957            if (mLayout != null) {
8958                output += "mLayout width=" + mLayout.getWidth()
8959                        + " height=" + mLayout.getHeight();
8960            }
8961        } else {
8962            output += "mText=NULL";
8963        }
8964        Log.d(VIEW_LOG_TAG, output);
8965    }
8966
8967    /**
8968     * Convenience for {@link Selection#getSelectionStart}.
8969     */
8970    @ViewDebug.ExportedProperty(category = "text")
8971    public int getSelectionStart() {
8972        return Selection.getSelectionStart(getText());
8973    }
8974
8975    /**
8976     * Convenience for {@link Selection#getSelectionEnd}.
8977     */
8978    @ViewDebug.ExportedProperty(category = "text")
8979    public int getSelectionEnd() {
8980        return Selection.getSelectionEnd(getText());
8981    }
8982
8983    /**
8984     * Return true iff there is a selection inside this text view.
8985     */
8986    public boolean hasSelection() {
8987        final int selectionStart = getSelectionStart();
8988        final int selectionEnd = getSelectionEnd();
8989
8990        return selectionStart >= 0 && selectionStart != selectionEnd;
8991    }
8992
8993    String getSelectedText() {
8994        if (!hasSelection()) {
8995            return null;
8996        }
8997
8998        final int start = getSelectionStart();
8999        final int end = getSelectionEnd();
9000        return String.valueOf(
9001                start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
9002    }
9003
9004    /**
9005     * Sets the properties of this field (lines, horizontally scrolling,
9006     * transformation method) to be for a single-line input.
9007     *
9008     * @attr ref android.R.styleable#TextView_singleLine
9009     */
9010    public void setSingleLine() {
9011        setSingleLine(true);
9012    }
9013
9014    /**
9015     * Sets the properties of this field to transform input to ALL CAPS
9016     * display. This may use a "small caps" formatting if available.
9017     * This setting will be ignored if this field is editable or selectable.
9018     *
9019     * This call replaces the current transformation method. Disabling this
9020     * will not necessarily restore the previous behavior from before this
9021     * was enabled.
9022     *
9023     * @see #setTransformationMethod(TransformationMethod)
9024     * @attr ref android.R.styleable#TextView_textAllCaps
9025     */
9026    public void setAllCaps(boolean allCaps) {
9027        if (allCaps) {
9028            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
9029        } else {
9030            setTransformationMethod(null);
9031        }
9032    }
9033
9034    /**
9035     *
9036     * Checks whether the transformation method applied to this TextView is set to ALL CAPS. This
9037     * settings is internally ignored if this field is editable or selectable.
9038     * @return Whether the current transformation method is for ALL CAPS.
9039     *
9040     * @see #setAllCaps(boolean)
9041     * @see #setTransformationMethod(TransformationMethod)
9042     */
9043    public boolean isAllCaps() {
9044        final TransformationMethod method = getTransformationMethod();
9045        return method != null && method instanceof AllCapsTransformationMethod;
9046    }
9047
9048    /**
9049     * If true, sets the properties of this field (number of lines, horizontally scrolling,
9050     * transformation method) to be for a single-line input; if false, restores these to the default
9051     * conditions.
9052     *
9053     * Note that the default conditions are not necessarily those that were in effect prior this
9054     * method, and you may want to reset these properties to your custom values.
9055     *
9056     * @attr ref android.R.styleable#TextView_singleLine
9057     */
9058    @android.view.RemotableViewMethod
9059    public void setSingleLine(boolean singleLine) {
9060        // Could be used, but may break backward compatibility.
9061        // if (mSingleLine == singleLine) return;
9062        setInputTypeSingleLine(singleLine);
9063        applySingleLine(singleLine, true, true);
9064    }
9065
9066    /**
9067     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
9068     * @param singleLine
9069     */
9070    private void setInputTypeSingleLine(boolean singleLine) {
9071        if (mEditor != null
9072                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9073                        == EditorInfo.TYPE_CLASS_TEXT) {
9074            if (singleLine) {
9075                mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9076            } else {
9077                mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9078            }
9079        }
9080    }
9081
9082    private void applySingleLine(boolean singleLine, boolean applyTransformation,
9083            boolean changeMaxLines) {
9084        mSingleLine = singleLine;
9085        if (singleLine) {
9086            setLines(1);
9087            setHorizontallyScrolling(true);
9088            if (applyTransformation) {
9089                setTransformationMethod(SingleLineTransformationMethod.getInstance());
9090            }
9091        } else {
9092            if (changeMaxLines) {
9093                setMaxLines(Integer.MAX_VALUE);
9094            }
9095            setHorizontallyScrolling(false);
9096            if (applyTransformation) {
9097                setTransformationMethod(null);
9098            }
9099        }
9100    }
9101
9102    /**
9103     * Causes words in the text that are longer than the view's width
9104     * to be ellipsized instead of broken in the middle.  You may also
9105     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
9106     * to constrain the text to a single line.  Use <code>null</code>
9107     * to turn off ellipsizing.
9108     *
9109     * If {@link #setMaxLines} has been used to set two or more lines,
9110     * only {@link android.text.TextUtils.TruncateAt#END} and
9111     * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
9112     * (other ellipsizing types will not do anything).
9113     *
9114     * @attr ref android.R.styleable#TextView_ellipsize
9115     */
9116    public void setEllipsize(TextUtils.TruncateAt where) {
9117        // TruncateAt is an enum. != comparison is ok between these singleton objects.
9118        if (mEllipsize != where) {
9119            mEllipsize = where;
9120
9121            if (mLayout != null) {
9122                nullLayouts();
9123                requestLayout();
9124                invalidate();
9125            }
9126        }
9127    }
9128
9129    /**
9130     * Sets how many times to repeat the marquee animation. Only applied if the
9131     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
9132     *
9133     * @see #getMarqueeRepeatLimit()
9134     *
9135     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9136     */
9137    public void setMarqueeRepeatLimit(int marqueeLimit) {
9138        mMarqueeRepeatLimit = marqueeLimit;
9139    }
9140
9141    /**
9142     * Gets the number of times the marquee animation is repeated. Only meaningful if the
9143     * TextView has marquee enabled.
9144     *
9145     * @return the number of times the marquee animation is repeated. -1 if the animation
9146     * repeats indefinitely
9147     *
9148     * @see #setMarqueeRepeatLimit(int)
9149     *
9150     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9151     */
9152    public int getMarqueeRepeatLimit() {
9153        return mMarqueeRepeatLimit;
9154    }
9155
9156    /**
9157     * Returns where, if anywhere, words that are longer than the view
9158     * is wide should be ellipsized.
9159     */
9160    @ViewDebug.ExportedProperty
9161    public TextUtils.TruncateAt getEllipsize() {
9162        return mEllipsize;
9163    }
9164
9165    /**
9166     * Set the TextView so that when it takes focus, all the text is
9167     * selected.
9168     *
9169     * @attr ref android.R.styleable#TextView_selectAllOnFocus
9170     */
9171    @android.view.RemotableViewMethod
9172    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
9173        createEditorIfNeeded();
9174        mEditor.mSelectAllOnFocus = selectAllOnFocus;
9175
9176        if (selectAllOnFocus && !(mText instanceof Spannable)) {
9177            setText(mText, BufferType.SPANNABLE);
9178        }
9179    }
9180
9181    /**
9182     * Set whether the cursor is visible. The default is true. Note that this property only
9183     * makes sense for editable TextView.
9184     *
9185     * @see #isCursorVisible()
9186     *
9187     * @attr ref android.R.styleable#TextView_cursorVisible
9188     */
9189    @android.view.RemotableViewMethod
9190    public void setCursorVisible(boolean visible) {
9191        if (visible && mEditor == null) return; // visible is the default value with no edit data
9192        createEditorIfNeeded();
9193        if (mEditor.mCursorVisible != visible) {
9194            mEditor.mCursorVisible = visible;
9195            invalidate();
9196
9197            mEditor.makeBlink();
9198
9199            // InsertionPointCursorController depends on mCursorVisible
9200            mEditor.prepareCursorControllers();
9201        }
9202    }
9203
9204    /**
9205     * @return whether or not the cursor is visible (assuming this TextView is editable)
9206     *
9207     * @see #setCursorVisible(boolean)
9208     *
9209     * @attr ref android.R.styleable#TextView_cursorVisible
9210     */
9211    public boolean isCursorVisible() {
9212        // true is the default value
9213        return mEditor == null ? true : mEditor.mCursorVisible;
9214    }
9215
9216    private boolean canMarquee() {
9217        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9218        return width > 0 && (mLayout.getLineWidth(0) > width
9219                || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
9220                        && mSavedMarqueeModeLayout.getLineWidth(0) > width));
9221    }
9222
9223    private void startMarquee() {
9224        // Do not ellipsize EditText
9225        if (getKeyListener() != null) return;
9226
9227        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
9228            return;
9229        }
9230
9231        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
9232                && getLineCount() == 1 && canMarquee()) {
9233
9234            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9235                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
9236                final Layout tmp = mLayout;
9237                mLayout = mSavedMarqueeModeLayout;
9238                mSavedMarqueeModeLayout = tmp;
9239                setHorizontalFadingEdgeEnabled(true);
9240                requestLayout();
9241                invalidate();
9242            }
9243
9244            if (mMarquee == null) mMarquee = new Marquee(this);
9245            mMarquee.start(mMarqueeRepeatLimit);
9246        }
9247    }
9248
9249    private void stopMarquee() {
9250        if (mMarquee != null && !mMarquee.isStopped()) {
9251            mMarquee.stop();
9252        }
9253
9254        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
9255            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9256            final Layout tmp = mSavedMarqueeModeLayout;
9257            mSavedMarqueeModeLayout = mLayout;
9258            mLayout = tmp;
9259            setHorizontalFadingEdgeEnabled(false);
9260            requestLayout();
9261            invalidate();
9262        }
9263    }
9264
9265    private void startStopMarquee(boolean start) {
9266        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9267            if (start) {
9268                startMarquee();
9269            } else {
9270                stopMarquee();
9271            }
9272        }
9273    }
9274
9275    /**
9276     * This method is called when the text is changed, in case any subclasses
9277     * would like to know.
9278     *
9279     * Within <code>text</code>, the <code>lengthAfter</code> characters
9280     * beginning at <code>start</code> have just replaced old text that had
9281     * length <code>lengthBefore</code>. It is an error to attempt to make
9282     * changes to <code>text</code> from this callback.
9283     *
9284     * @param text The text the TextView is displaying
9285     * @param start The offset of the start of the range of the text that was
9286     * modified
9287     * @param lengthBefore The length of the former text that has been replaced
9288     * @param lengthAfter The length of the replacement modified text
9289     */
9290    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
9291        // intentionally empty, template pattern method can be overridden by subclasses
9292    }
9293
9294    /**
9295     * This method is called when the selection has changed, in case any
9296     * subclasses would like to know.
9297     *
9298     * @param selStart The new selection start location.
9299     * @param selEnd The new selection end location.
9300     */
9301    protected void onSelectionChanged(int selStart, int selEnd) {
9302        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
9303    }
9304
9305    /**
9306     * Adds a TextWatcher to the list of those whose methods are called
9307     * whenever this TextView's text changes.
9308     * <p>
9309     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
9310     * not called after {@link #setText} calls.  Now, doing {@link #setText}
9311     * if there are any text changed listeners forces the buffer type to
9312     * Editable if it would not otherwise be and does call this method.
9313     */
9314    public void addTextChangedListener(TextWatcher watcher) {
9315        if (mListeners == null) {
9316            mListeners = new ArrayList<TextWatcher>();
9317        }
9318
9319        mListeners.add(watcher);
9320    }
9321
9322    /**
9323     * Removes the specified TextWatcher from the list of those whose
9324     * methods are called
9325     * whenever this TextView's text changes.
9326     */
9327    public void removeTextChangedListener(TextWatcher watcher) {
9328        if (mListeners != null) {
9329            int i = mListeners.indexOf(watcher);
9330
9331            if (i >= 0) {
9332                mListeners.remove(i);
9333            }
9334        }
9335    }
9336
9337    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
9338        if (mListeners != null) {
9339            final ArrayList<TextWatcher> list = mListeners;
9340            final int count = list.size();
9341            for (int i = 0; i < count; i++) {
9342                list.get(i).beforeTextChanged(text, start, before, after);
9343            }
9344        }
9345
9346        // The spans that are inside or intersect the modified region no longer make sense
9347        removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
9348        removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
9349    }
9350
9351    // Removes all spans that are inside or actually overlap the start..end range
9352    private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
9353        if (!(mText instanceof Editable)) return;
9354        Editable text = (Editable) mText;
9355
9356        T[] spans = text.getSpans(start, end, type);
9357        final int length = spans.length;
9358        for (int i = 0; i < length; i++) {
9359            final int spanStart = text.getSpanStart(spans[i]);
9360            final int spanEnd = text.getSpanEnd(spans[i]);
9361            if (spanEnd == start || spanStart == end) break;
9362            text.removeSpan(spans[i]);
9363        }
9364    }
9365
9366    void removeAdjacentSuggestionSpans(final int pos) {
9367        if (!(mText instanceof Editable)) return;
9368        final Editable text = (Editable) mText;
9369
9370        final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
9371        final int length = spans.length;
9372        for (int i = 0; i < length; i++) {
9373            final int spanStart = text.getSpanStart(spans[i]);
9374            final int spanEnd = text.getSpanEnd(spans[i]);
9375            if (spanEnd == pos || spanStart == pos) {
9376                if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
9377                    text.removeSpan(spans[i]);
9378                }
9379            }
9380        }
9381    }
9382
9383    /**
9384     * Not private so it can be called from an inner class without going
9385     * through a thunk.
9386     */
9387    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
9388        if (mListeners != null) {
9389            final ArrayList<TextWatcher> list = mListeners;
9390            final int count = list.size();
9391            for (int i = 0; i < count; i++) {
9392                list.get(i).onTextChanged(text, start, before, after);
9393            }
9394        }
9395
9396        if (mEditor != null) mEditor.sendOnTextChanged(start, after);
9397    }
9398
9399    /**
9400     * Not private so it can be called from an inner class without going
9401     * through a thunk.
9402     */
9403    void sendAfterTextChanged(Editable text) {
9404        if (mListeners != null) {
9405            final ArrayList<TextWatcher> list = mListeners;
9406            final int count = list.size();
9407            for (int i = 0; i < count; i++) {
9408                list.get(i).afterTextChanged(text);
9409            }
9410        }
9411
9412        // Always notify AutoFillManager - it will return right away if autofill is disabled.
9413        notifyAutoFillManagerAfterTextChangedIfNeeded();
9414
9415        hideErrorIfUnchanged();
9416    }
9417
9418    private void notifyAutoFillManagerAfterTextChangedIfNeeded() {
9419        // It is important to not check whether the view is important for autofill
9420        // since the user can trigger autofill manually on not important views.
9421        if (!isAutofillable()) {
9422            return;
9423        }
9424        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
9425        if (afm != null) {
9426            if (DEBUG_AUTOFILL) {
9427                Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
9428            }
9429            afm.notifyValueChanged(TextView.this);
9430        }
9431    }
9432
9433    private boolean isAutofillable() {
9434        // It is important to not check whether the view is important for autofill
9435        // since the user can trigger autofill manually on not important views.
9436        return getAutofillType() != AUTOFILL_TYPE_NONE;
9437    }
9438
9439    void updateAfterEdit() {
9440        invalidate();
9441        int curs = getSelectionStart();
9442
9443        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9444            registerForPreDraw();
9445        }
9446
9447        checkForResize();
9448
9449        if (curs >= 0) {
9450            mHighlightPathBogus = true;
9451            if (mEditor != null) mEditor.makeBlink();
9452            bringPointIntoView(curs);
9453        }
9454    }
9455
9456    /**
9457     * Not private so it can be called from an inner class without going
9458     * through a thunk.
9459     */
9460    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
9461        sLastCutCopyOrTextChangedTime = 0;
9462
9463        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9464        if (ims == null || ims.mBatchEditNesting == 0) {
9465            updateAfterEdit();
9466        }
9467        if (ims != null) {
9468            ims.mContentChanged = true;
9469            if (ims.mChangedStart < 0) {
9470                ims.mChangedStart = start;
9471                ims.mChangedEnd = start + before;
9472            } else {
9473                ims.mChangedStart = Math.min(ims.mChangedStart, start);
9474                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
9475            }
9476            ims.mChangedDelta += after - before;
9477        }
9478        resetErrorChangedFlag();
9479        sendOnTextChanged(buffer, start, before, after);
9480        onTextChanged(buffer, start, before, after);
9481    }
9482
9483    /**
9484     * Not private so it can be called from an inner class without going
9485     * through a thunk.
9486     */
9487    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
9488        // XXX Make the start and end move together if this ends up
9489        // spending too much time invalidating.
9490
9491        boolean selChanged = false;
9492        int newSelStart = -1, newSelEnd = -1;
9493
9494        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9495
9496        if (what == Selection.SELECTION_END) {
9497            selChanged = true;
9498            newSelEnd = newStart;
9499
9500            if (oldStart >= 0 || newStart >= 0) {
9501                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
9502                checkForResize();
9503                registerForPreDraw();
9504                if (mEditor != null) mEditor.makeBlink();
9505            }
9506        }
9507
9508        if (what == Selection.SELECTION_START) {
9509            selChanged = true;
9510            newSelStart = newStart;
9511
9512            if (oldStart >= 0 || newStart >= 0) {
9513                int end = Selection.getSelectionEnd(buf);
9514                invalidateCursor(end, oldStart, newStart);
9515            }
9516        }
9517
9518        if (selChanged) {
9519            mHighlightPathBogus = true;
9520            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
9521
9522            if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
9523                if (newSelStart < 0) {
9524                    newSelStart = Selection.getSelectionStart(buf);
9525                }
9526                if (newSelEnd < 0) {
9527                    newSelEnd = Selection.getSelectionEnd(buf);
9528                }
9529
9530                if (mEditor != null) {
9531                    mEditor.refreshTextActionMode();
9532                    if (!hasSelection()
9533                            && mEditor.getTextActionMode() == null && hasTransientState()) {
9534                        // User generated selection has been removed.
9535                        setHasTransientState(false);
9536                    }
9537                }
9538                onSelectionChanged(newSelStart, newSelEnd);
9539            }
9540        }
9541
9542        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
9543                || what instanceof CharacterStyle) {
9544            if (ims == null || ims.mBatchEditNesting == 0) {
9545                invalidate();
9546                mHighlightPathBogus = true;
9547                checkForResize();
9548            } else {
9549                ims.mContentChanged = true;
9550            }
9551            if (mEditor != null) {
9552                if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
9553                if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
9554                mEditor.invalidateHandlesAndActionMode();
9555            }
9556        }
9557
9558        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
9559            mHighlightPathBogus = true;
9560            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
9561                ims.mSelectionModeChanged = true;
9562            }
9563
9564            if (Selection.getSelectionStart(buf) >= 0) {
9565                if (ims == null || ims.mBatchEditNesting == 0) {
9566                    invalidateCursor();
9567                } else {
9568                    ims.mCursorChanged = true;
9569                }
9570            }
9571        }
9572
9573        if (what instanceof ParcelableSpan) {
9574            // If this is a span that can be sent to a remote process,
9575            // the current extract editor would be interested in it.
9576            if (ims != null && ims.mExtractedTextRequest != null) {
9577                if (ims.mBatchEditNesting != 0) {
9578                    if (oldStart >= 0) {
9579                        if (ims.mChangedStart > oldStart) {
9580                            ims.mChangedStart = oldStart;
9581                        }
9582                        if (ims.mChangedStart > oldEnd) {
9583                            ims.mChangedStart = oldEnd;
9584                        }
9585                    }
9586                    if (newStart >= 0) {
9587                        if (ims.mChangedStart > newStart) {
9588                            ims.mChangedStart = newStart;
9589                        }
9590                        if (ims.mChangedStart > newEnd) {
9591                            ims.mChangedStart = newEnd;
9592                        }
9593                    }
9594                } else {
9595                    if (DEBUG_EXTRACT) {
9596                        Log.v(LOG_TAG, "Span change outside of batch: "
9597                                + oldStart + "-" + oldEnd + ","
9598                                + newStart + "-" + newEnd + " " + what);
9599                    }
9600                    ims.mContentChanged = true;
9601                }
9602            }
9603        }
9604
9605        if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
9606                && what instanceof SpellCheckSpan) {
9607            mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
9608        }
9609    }
9610
9611    @Override
9612    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
9613        if (isTemporarilyDetached()) {
9614            // If we are temporarily in the detach state, then do nothing.
9615            super.onFocusChanged(focused, direction, previouslyFocusedRect);
9616            return;
9617        }
9618
9619        if (mEditor != null) mEditor.onFocusChanged(focused, direction);
9620
9621        if (focused) {
9622            if (mText instanceof Spannable) {
9623                Spannable sp = (Spannable) mText;
9624                MetaKeyKeyListener.resetMetaState(sp);
9625            }
9626        }
9627
9628        startStopMarquee(focused);
9629
9630        if (mTransformation != null) {
9631            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
9632        }
9633
9634        super.onFocusChanged(focused, direction, previouslyFocusedRect);
9635    }
9636
9637    @Override
9638    public void onWindowFocusChanged(boolean hasWindowFocus) {
9639        super.onWindowFocusChanged(hasWindowFocus);
9640
9641        if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
9642
9643        startStopMarquee(hasWindowFocus);
9644    }
9645
9646    @Override
9647    protected void onVisibilityChanged(View changedView, int visibility) {
9648        super.onVisibilityChanged(changedView, visibility);
9649        if (mEditor != null && visibility != VISIBLE) {
9650            mEditor.hideCursorAndSpanControllers();
9651            stopTextActionMode();
9652        }
9653    }
9654
9655    /**
9656     * Use {@link BaseInputConnection#removeComposingSpans
9657     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
9658     * state from this text view.
9659     */
9660    public void clearComposingText() {
9661        if (mText instanceof Spannable) {
9662            BaseInputConnection.removeComposingSpans((Spannable) mText);
9663        }
9664    }
9665
9666    @Override
9667    public void setSelected(boolean selected) {
9668        boolean wasSelected = isSelected();
9669
9670        super.setSelected(selected);
9671
9672        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9673            if (selected) {
9674                startMarquee();
9675            } else {
9676                stopMarquee();
9677            }
9678        }
9679    }
9680
9681    @Override
9682    public boolean onTouchEvent(MotionEvent event) {
9683        final int action = event.getActionMasked();
9684        if (mEditor != null) {
9685            mEditor.onTouchEvent(event);
9686
9687            if (mEditor.mSelectionModifierCursorController != null
9688                    && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
9689                return true;
9690            }
9691        }
9692
9693        final boolean superResult = super.onTouchEvent(event);
9694
9695        /*
9696         * Don't handle the release after a long press, because it will move the selection away from
9697         * whatever the menu action was trying to affect. If the long press should have triggered an
9698         * insertion action mode, we can now actually show it.
9699         */
9700        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
9701            mEditor.mDiscardNextActionUp = false;
9702
9703            if (mEditor.mIsInsertionActionModeStartPending) {
9704                mEditor.startInsertionActionMode();
9705                mEditor.mIsInsertionActionModeStartPending = false;
9706            }
9707            return superResult;
9708        }
9709
9710        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
9711                && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
9712
9713        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
9714                && mText instanceof Spannable && mLayout != null) {
9715            boolean handled = false;
9716
9717            if (mMovement != null) {
9718                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
9719            }
9720
9721            final boolean textIsSelectable = isTextSelectable();
9722            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
9723                // The LinkMovementMethod which should handle taps on links has not been installed
9724                // on non editable text that support text selection.
9725                // We reproduce its behavior here to open links for these.
9726                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
9727                    getSelectionEnd(), ClickableSpan.class);
9728
9729                if (links.length > 0) {
9730                    links[0].onClick(this);
9731                    handled = true;
9732                }
9733            }
9734
9735            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
9736                // Show the IME, except when selecting in read-only text.
9737                final InputMethodManager imm = InputMethodManager.peekInstance();
9738                viewClicked(imm);
9739                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9740                    imm.showSoftInput(this, 0);
9741                }
9742
9743                // The above condition ensures that the mEditor is not null
9744                mEditor.onTouchUpEvent(event);
9745
9746                handled = true;
9747            }
9748
9749            if (handled) {
9750                return true;
9751            }
9752        }
9753
9754        return superResult;
9755    }
9756
9757    @Override
9758    public boolean onGenericMotionEvent(MotionEvent event) {
9759        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9760            try {
9761                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
9762                    return true;
9763                }
9764            } catch (AbstractMethodError ex) {
9765                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
9766                // Ignore its absence in case third party applications implemented the
9767                // interface directly.
9768            }
9769        }
9770        return super.onGenericMotionEvent(event);
9771    }
9772
9773    @Override
9774    protected void onCreateContextMenu(ContextMenu menu) {
9775        if (mEditor != null) {
9776            mEditor.onCreateContextMenu(menu);
9777        }
9778    }
9779
9780    @Override
9781    public boolean showContextMenu() {
9782        if (mEditor != null) {
9783            mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
9784        }
9785        return super.showContextMenu();
9786    }
9787
9788    @Override
9789    public boolean showContextMenu(float x, float y) {
9790        if (mEditor != null) {
9791            mEditor.setContextMenuAnchor(x, y);
9792        }
9793        return super.showContextMenu(x, y);
9794    }
9795
9796    /**
9797     * @return True iff this TextView contains a text that can be edited, or if this is
9798     * a selectable TextView.
9799     */
9800    boolean isTextEditable() {
9801        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
9802    }
9803
9804    /**
9805     * Returns true, only while processing a touch gesture, if the initial
9806     * touch down event caused focus to move to the text view and as a result
9807     * its selection changed.  Only valid while processing the touch gesture
9808     * of interest, in an editable text view.
9809     */
9810    public boolean didTouchFocusSelect() {
9811        return mEditor != null && mEditor.mTouchFocusSelected;
9812    }
9813
9814    @Override
9815    public void cancelLongPress() {
9816        super.cancelLongPress();
9817        if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
9818    }
9819
9820    @Override
9821    public boolean onTrackballEvent(MotionEvent event) {
9822        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9823            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
9824                return true;
9825            }
9826        }
9827
9828        return super.onTrackballEvent(event);
9829    }
9830
9831    /**
9832     * Sets the Scroller used for producing a scrolling animation
9833     *
9834     * @param s A Scroller instance
9835     */
9836    public void setScroller(Scroller s) {
9837        mScroller = s;
9838    }
9839
9840    @Override
9841    protected float getLeftFadingEdgeStrength() {
9842        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9843            final Marquee marquee = mMarquee;
9844            if (marquee.shouldDrawLeftFade()) {
9845                return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
9846            } else {
9847                return 0.0f;
9848            }
9849        } else if (getLineCount() == 1) {
9850            final float lineLeft = getLayout().getLineLeft(0);
9851            if (lineLeft > mScrollX) return 0.0f;
9852            return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
9853        }
9854        return super.getLeftFadingEdgeStrength();
9855    }
9856
9857    @Override
9858    protected float getRightFadingEdgeStrength() {
9859        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9860            final Marquee marquee = mMarquee;
9861            return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
9862        } else if (getLineCount() == 1) {
9863            final float rightEdge = mScrollX +
9864                    (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
9865            final float lineRight = getLayout().getLineRight(0);
9866            if (lineRight < rightEdge) return 0.0f;
9867            return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
9868        }
9869        return super.getRightFadingEdgeStrength();
9870    }
9871
9872    /**
9873     * Calculates the fading edge strength as the ratio of the distance between two
9874     * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
9875     * value for the distance calculation.
9876     *
9877     * @param position1 A horizontal position.
9878     * @param position2 A horizontal position.
9879     * @return Fading edge strength between [0.0f, 1.0f].
9880     */
9881    @FloatRange(from = 0.0, to = 1.0)
9882    private float getHorizontalFadingEdgeStrength(float position1, float position2) {
9883        final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
9884        if (horizontalFadingEdgeLength == 0) return 0.0f;
9885        final float diff = Math.abs(position1 - position2);
9886        if (diff > horizontalFadingEdgeLength) return 1.0f;
9887        return diff / horizontalFadingEdgeLength;
9888    }
9889
9890    private boolean isMarqueeFadeEnabled() {
9891        return mEllipsize == TextUtils.TruncateAt.MARQUEE
9892                && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9893    }
9894
9895    @Override
9896    protected int computeHorizontalScrollRange() {
9897        if (mLayout != null) {
9898            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
9899                    ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
9900        }
9901
9902        return super.computeHorizontalScrollRange();
9903    }
9904
9905    @Override
9906    protected int computeVerticalScrollRange() {
9907        if (mLayout != null) {
9908            return mLayout.getHeight();
9909        }
9910        return super.computeVerticalScrollRange();
9911    }
9912
9913    @Override
9914    protected int computeVerticalScrollExtent() {
9915        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
9916    }
9917
9918    @Override
9919    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
9920        super.findViewsWithText(outViews, searched, flags);
9921        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
9922                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
9923            String searchedLowerCase = searched.toString().toLowerCase();
9924            String textLowerCase = mText.toString().toLowerCase();
9925            if (textLowerCase.contains(searchedLowerCase)) {
9926                outViews.add(this);
9927            }
9928        }
9929    }
9930
9931    /**
9932     * Type of the text buffer that defines the characteristics of the text such as static,
9933     * styleable, or editable.
9934     */
9935    public enum BufferType {
9936        NORMAL, SPANNABLE, EDITABLE
9937    }
9938
9939    /**
9940     * Returns the TextView_textColor attribute from the TypedArray, if set, or
9941     * the TextAppearance_textColor from the TextView_textAppearance attribute,
9942     * if TextView_textColor was not set directly.
9943     *
9944     * @removed
9945     */
9946    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
9947        if (attrs == null) {
9948            // Preserve behavior prior to removal of this API.
9949            throw new NullPointerException();
9950        }
9951
9952        // It's not safe to use this method from apps. The parameter 'attrs'
9953        // must have been obtained using the TextView filter array which is not
9954        // available to the SDK. As such, we grab a default TypedArray with the
9955        // right filter instead here.
9956        final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
9957        ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
9958        if (colors == null) {
9959            final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
9960            if (ap != 0) {
9961                final TypedArray appearance = context.obtainStyledAttributes(
9962                        ap, R.styleable.TextAppearance);
9963                colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
9964                appearance.recycle();
9965            }
9966        }
9967        a.recycle();
9968
9969        return colors;
9970    }
9971
9972    /**
9973     * Returns the default color from the TextView_textColor attribute from the
9974     * AttributeSet, if set, or the default color from the
9975     * TextAppearance_textColor from the TextView_textAppearance attribute, if
9976     * TextView_textColor was not set directly.
9977     *
9978     * @removed
9979     */
9980    public static int getTextColor(Context context, TypedArray attrs, int def) {
9981        final ColorStateList colors = getTextColors(context, attrs);
9982        if (colors == null) {
9983            return def;
9984        } else {
9985            return colors.getDefaultColor();
9986        }
9987    }
9988
9989    @Override
9990    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
9991        if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
9992            // Handle Ctrl-only shortcuts.
9993            switch (keyCode) {
9994                case KeyEvent.KEYCODE_A:
9995                    if (canSelectText()) {
9996                        return onTextContextMenuItem(ID_SELECT_ALL);
9997                    }
9998                    break;
9999                case KeyEvent.KEYCODE_Z:
10000                    if (canUndo()) {
10001                        return onTextContextMenuItem(ID_UNDO);
10002                    }
10003                    break;
10004                case KeyEvent.KEYCODE_X:
10005                    if (canCut()) {
10006                        return onTextContextMenuItem(ID_CUT);
10007                    }
10008                    break;
10009                case KeyEvent.KEYCODE_C:
10010                    if (canCopy()) {
10011                        return onTextContextMenuItem(ID_COPY);
10012                    }
10013                    break;
10014                case KeyEvent.KEYCODE_V:
10015                    if (canPaste()) {
10016                        return onTextContextMenuItem(ID_PASTE);
10017                    }
10018                    break;
10019            }
10020        } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
10021            // Handle Ctrl-Shift shortcuts.
10022            switch (keyCode) {
10023                case KeyEvent.KEYCODE_Z:
10024                    if (canRedo()) {
10025                        return onTextContextMenuItem(ID_REDO);
10026                    }
10027                    break;
10028                case KeyEvent.KEYCODE_V:
10029                    if (canPaste()) {
10030                        return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
10031                    }
10032            }
10033        }
10034        return super.onKeyShortcut(keyCode, event);
10035    }
10036
10037    /**
10038     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
10039     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
10040     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
10041     * sufficient.
10042     */
10043    boolean canSelectText() {
10044        return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
10045    }
10046
10047    /**
10048     * Test based on the <i>intrinsic</i> charateristics of the TextView.
10049     * The text must be spannable and the movement method must allow for arbitary selection.
10050     *
10051     * See also {@link #canSelectText()}.
10052     */
10053    boolean textCanBeSelected() {
10054        // prepareCursorController() relies on this method.
10055        // If you change this condition, make sure prepareCursorController is called anywhere
10056        // the value of this condition might be changed.
10057        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
10058        return isTextEditable()
10059                || (isTextSelectable() && mText instanceof Spannable && isEnabled());
10060    }
10061
10062    private Locale getTextServicesLocale(boolean allowNullLocale) {
10063        // Start fetching the text services locale asynchronously.
10064        updateTextServicesLocaleAsync();
10065        // If !allowNullLocale and there is no cached text services locale, just return the default
10066        // locale.
10067        return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
10068                : mCurrentSpellCheckerLocaleCache;
10069    }
10070
10071    /**
10072     * This is a temporary method. Future versions may support multi-locale text.
10073     * Caveat: This method may not return the latest text services locale, but this should be
10074     * acceptable and it's more important to make this method asynchronous.
10075     *
10076     * @return The locale that should be used for a word iterator
10077     * in this TextView, based on the current spell checker settings,
10078     * the current IME's locale, or the system default locale.
10079     * Please note that a word iterator in this TextView is different from another word iterator
10080     * used by SpellChecker.java of TextView. This method should be used for the former.
10081     * @hide
10082     */
10083    // TODO: Support multi-locale
10084    // TODO: Update the text services locale immediately after the keyboard locale is switched
10085    // by catching intent of keyboard switch event
10086    public Locale getTextServicesLocale() {
10087        return getTextServicesLocale(false /* allowNullLocale */);
10088    }
10089
10090    /**
10091     * @return {@code true} if this TextView is specialized for showing and interacting with the
10092     * extracted text in a full-screen input method.
10093     * @hide
10094     */
10095    public boolean isInExtractedMode() {
10096        return false;
10097    }
10098
10099    /**
10100     * @return {@code true} if this widget supports auto-sizing text and has been configured to
10101     * auto-size.
10102     */
10103    private boolean isAutoSizeEnabled() {
10104        return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
10105    }
10106
10107    /**
10108     * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
10109     * @hide
10110     */
10111    protected boolean supportsAutoSizeText() {
10112        return true;
10113    }
10114
10115    /**
10116     * This is a temporary method. Future versions may support multi-locale text.
10117     * Caveat: This method may not return the latest spell checker locale, but this should be
10118     * acceptable and it's more important to make this method asynchronous.
10119     *
10120     * @return The locale that should be used for a spell checker in this TextView,
10121     * based on the current spell checker settings, the current IME's locale, or the system default
10122     * locale.
10123     * @hide
10124     */
10125    public Locale getSpellCheckerLocale() {
10126        return getTextServicesLocale(true /* allowNullLocale */);
10127    }
10128
10129    private void updateTextServicesLocaleAsync() {
10130        // AsyncTask.execute() uses a serial executor which means we don't have
10131        // to lock around updateTextServicesLocaleLocked() to prevent it from
10132        // being executed n times in parallel.
10133        AsyncTask.execute(new Runnable() {
10134            @Override
10135            public void run() {
10136                updateTextServicesLocaleLocked();
10137            }
10138        });
10139    }
10140
10141    private void updateTextServicesLocaleLocked() {
10142        final TextServicesManager textServicesManager = (TextServicesManager)
10143                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
10144        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
10145        final Locale locale;
10146        if (subtype != null) {
10147            locale = subtype.getLocaleObject();
10148        } else {
10149            locale = null;
10150        }
10151        mCurrentSpellCheckerLocaleCache = locale;
10152    }
10153
10154    void onLocaleChanged() {
10155        mEditor.onLocaleChanged();
10156    }
10157
10158    /**
10159     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
10160     * Made available to achieve a consistent behavior.
10161     * @hide
10162     */
10163    public WordIterator getWordIterator() {
10164        if (mEditor != null) {
10165            return mEditor.getWordIterator();
10166        } else {
10167            return null;
10168        }
10169    }
10170
10171    /** @hide */
10172    @Override
10173    public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
10174        super.onPopulateAccessibilityEventInternal(event);
10175
10176        final CharSequence text = getTextForAccessibility();
10177        if (!TextUtils.isEmpty(text)) {
10178            event.getText().add(text);
10179        }
10180    }
10181
10182    @Override
10183    public CharSequence getAccessibilityClassName() {
10184        return TextView.class.getName();
10185    }
10186
10187    @Override
10188    public void onProvideStructure(ViewStructure structure) {
10189        super.onProvideStructure(structure);
10190        onProvideAutoStructureForAssistOrAutofill(structure, false);
10191    }
10192
10193    @Override
10194    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
10195        super.onProvideAutofillStructure(structure, flags);
10196        onProvideAutoStructureForAssistOrAutofill(structure, true);
10197    }
10198
10199    private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure,
10200            boolean forAutofill) {
10201        final boolean isPassword = hasPasswordTransformationMethod()
10202                || isPasswordInputType(getInputType());
10203        if (forAutofill) {
10204            structure.setDataIsSensitive(!mTextFromResource);
10205        }
10206
10207        if (!isPassword || forAutofill) {
10208            if (mLayout == null) {
10209                assumeLayout();
10210            }
10211            Layout layout = mLayout;
10212            final int lineCount = layout.getLineCount();
10213            if (lineCount <= 1) {
10214                // Simple case: this is a single line.
10215                final CharSequence text = getText();
10216                if (forAutofill) {
10217                    structure.setText(text);
10218                } else {
10219                    structure.setText(text, getSelectionStart(), getSelectionEnd());
10220                }
10221            } else {
10222                // Complex case: multi-line, could be scrolled or within a scroll container
10223                // so some lines are not visible.
10224                final int[] tmpCords = new int[2];
10225                getLocationInWindow(tmpCords);
10226                final int topWindowLocation = tmpCords[1];
10227                View root = this;
10228                ViewParent viewParent = getParent();
10229                while (viewParent instanceof View) {
10230                    root = (View) viewParent;
10231                    viewParent = root.getParent();
10232                }
10233                final int windowHeight = root.getHeight();
10234                final int topLine;
10235                final int bottomLine;
10236                if (topWindowLocation >= 0) {
10237                    // The top of the view is fully within its window; start text at line 0.
10238                    topLine = getLineAtCoordinateUnclamped(0);
10239                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
10240                } else {
10241                    // The top of hte window has scrolled off the top of the window; figure out
10242                    // the starting line for this.
10243                    topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
10244                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
10245                }
10246                // We want to return some contextual lines above/below the lines that are
10247                // actually visible.
10248                int expandedTopLine = topLine - (bottomLine - topLine) / 2;
10249                if (expandedTopLine < 0) {
10250                    expandedTopLine = 0;
10251                }
10252                int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
10253                if (expandedBottomLine >= lineCount) {
10254                    expandedBottomLine = lineCount - 1;
10255                }
10256
10257                // Convert lines into character offsets.
10258                int expandedTopChar = layout.getLineStart(expandedTopLine);
10259                int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
10260
10261                // Take into account selection -- if there is a selection, we need to expand
10262                // the text we are returning to include that selection.
10263                final int selStart = getSelectionStart();
10264                final int selEnd = getSelectionEnd();
10265                if (selStart < selEnd) {
10266                    if (selStart < expandedTopChar) {
10267                        expandedTopChar = selStart;
10268                    }
10269                    if (selEnd > expandedBottomChar) {
10270                        expandedBottomChar = selEnd;
10271                    }
10272                }
10273
10274                // Get the text and trim it to the range we are reporting.
10275                CharSequence text = getText();
10276                if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
10277                    text = text.subSequence(expandedTopChar, expandedBottomChar);
10278                }
10279
10280                if (forAutofill) {
10281                    structure.setText(text);
10282                } else {
10283                    structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
10284
10285                    final int[] lineOffsets = new int[bottomLine - topLine + 1];
10286                    final int[] lineBaselines = new int[bottomLine - topLine + 1];
10287                    final int baselineOffset = getBaselineOffset();
10288                    for (int i = topLine; i <= bottomLine; i++) {
10289                        lineOffsets[i - topLine] = layout.getLineStart(i);
10290                        lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
10291                    }
10292                    structure.setTextLines(lineOffsets, lineBaselines);
10293                }
10294            }
10295
10296            if (!forAutofill) {
10297                // Extract style information that applies to the TextView as a whole.
10298                int style = 0;
10299                int typefaceStyle = getTypefaceStyle();
10300                if ((typefaceStyle & Typeface.BOLD) != 0) {
10301                    style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10302                }
10303                if ((typefaceStyle & Typeface.ITALIC) != 0) {
10304                    style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
10305                }
10306
10307                // Global styles can also be set via TextView.setPaintFlags().
10308                int paintFlags = mTextPaint.getFlags();
10309                if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
10310                    style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10311                }
10312                if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
10313                    style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
10314                }
10315                if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
10316                    style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
10317                }
10318
10319                // TextView does not have its own text background color. A background is either part
10320                // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
10321                structure.setTextStyle(getTextSize(), getCurrentTextColor(),
10322                        AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
10323            }
10324        }
10325        structure.setHint(getHint());
10326        structure.setInputType(getInputType());
10327    }
10328
10329    boolean canRequestAutofill() {
10330        if (!isAutofillable()) {
10331            return false;
10332        }
10333        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10334        if (afm != null) {
10335            return afm.isEnabled();
10336        }
10337        return false;
10338    }
10339
10340    private void requestAutofill() {
10341        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10342        if (afm != null) {
10343            afm.requestAutofill(this);
10344        }
10345    }
10346
10347    @Override
10348    public void autofill(AutofillValue value) {
10349        if (!value.isText() || !isTextEditable()) {
10350            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
10351            return;
10352        }
10353
10354        final CharSequence autofilledValue = value.getTextValue();
10355
10356        // First autofill it...
10357        setText(autofilledValue, mBufferType, true, 0);
10358
10359        // ...then move cursor to the end.
10360        final CharSequence text = getText();
10361        if ((text instanceof Spannable)) {
10362            Selection.setSelection((Spannable) text, text.length());
10363        }
10364    }
10365
10366    @Override
10367    public @AutofillType int getAutofillType() {
10368        return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
10369    }
10370
10371    /**
10372     * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
10373     * {@code char}s if longer.
10374     *
10375     * @return current text, {@code null} if the text is not editable
10376     *
10377     * @see View#getAutofillValue()
10378     */
10379    @Override
10380    @Nullable
10381    public AutofillValue getAutofillValue() {
10382        if (isTextEditable()) {
10383            final CharSequence text = TextUtils.trimToParcelableSize(getText());
10384            return AutofillValue.forText(text);
10385        }
10386        return null;
10387    }
10388
10389    /** @hide */
10390    @Override
10391    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
10392        super.onInitializeAccessibilityEventInternal(event);
10393
10394        final boolean isPassword = hasPasswordTransformationMethod();
10395        event.setPassword(isPassword);
10396
10397        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
10398            event.setFromIndex(Selection.getSelectionStart(mText));
10399            event.setToIndex(Selection.getSelectionEnd(mText));
10400            event.setItemCount(mText.length());
10401        }
10402    }
10403
10404    /** @hide */
10405    @Override
10406    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
10407        super.onInitializeAccessibilityNodeInfoInternal(info);
10408
10409        final boolean isPassword = hasPasswordTransformationMethod();
10410        info.setPassword(isPassword);
10411        info.setText(getTextForAccessibility());
10412        info.setHintText(mHint);
10413        info.setShowingHintText(isShowingHint());
10414
10415        if (mBufferType == BufferType.EDITABLE) {
10416            info.setEditable(true);
10417            if (isEnabled()) {
10418                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
10419            }
10420        }
10421
10422        if (mEditor != null) {
10423            info.setInputType(mEditor.mInputType);
10424
10425            if (mEditor.mError != null) {
10426                info.setContentInvalid(true);
10427                info.setError(mEditor.mError);
10428            }
10429        }
10430
10431        if (!TextUtils.isEmpty(mText)) {
10432            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
10433            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
10434            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
10435                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
10436                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
10437                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
10438                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
10439            info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
10440            info.setAvailableExtraData(
10441                    Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
10442        }
10443
10444        if (isFocused()) {
10445            if (canCopy()) {
10446                info.addAction(AccessibilityNodeInfo.ACTION_COPY);
10447            }
10448            if (canPaste()) {
10449                info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
10450            }
10451            if (canCut()) {
10452                info.addAction(AccessibilityNodeInfo.ACTION_CUT);
10453            }
10454            if (canShare()) {
10455                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
10456                        ACCESSIBILITY_ACTION_SHARE,
10457                        getResources().getString(com.android.internal.R.string.share)));
10458            }
10459            if (canProcessText()) {  // also implies mEditor is not null.
10460                mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
10461            }
10462        }
10463
10464        // Check for known input filter types.
10465        final int numFilters = mFilters.length;
10466        for (int i = 0; i < numFilters; i++) {
10467            final InputFilter filter = mFilters[i];
10468            if (filter instanceof InputFilter.LengthFilter) {
10469                info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
10470            }
10471        }
10472
10473        if (!isSingleLine()) {
10474            info.setMultiLine(true);
10475        }
10476    }
10477
10478    @Override
10479    public void addExtraDataToAccessibilityNodeInfo(
10480            AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
10481        // The only extra data we support requires arguments.
10482        if (arguments == null) {
10483            return;
10484        }
10485        if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
10486            int positionInfoStartIndex = arguments.getInt(
10487                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
10488            int positionInfoLength = arguments.getInt(
10489                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
10490            if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
10491                    || (positionInfoStartIndex >= mText.length())) {
10492                Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
10493                return;
10494            }
10495            RectF[] boundingRects = new RectF[positionInfoLength];
10496            final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
10497            populateCharacterBounds(builder, positionInfoStartIndex,
10498                    positionInfoStartIndex + positionInfoLength,
10499                    viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
10500            CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
10501            for (int i = 0; i < positionInfoLength; i++) {
10502                int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
10503                if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
10504                    RectF bounds = cursorAnchorInfo
10505                            .getCharacterBounds(positionInfoStartIndex + i);
10506                    if (bounds != null) {
10507                        mapRectFromViewToScreenCoords(bounds, true);
10508                        boundingRects[i] = bounds;
10509                    }
10510                }
10511            }
10512            info.getExtras().putParcelableArray(extraDataKey, boundingRects);
10513        }
10514    }
10515
10516    /**
10517     * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
10518     *
10519     * @param builder The builder to populate
10520     * @param startIndex The starting character index to populate
10521     * @param endIndex The ending character index to populate
10522     * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
10523     * content
10524     * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
10525     * @hide
10526     */
10527    public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
10528            int startIndex, int endIndex, float viewportToContentHorizontalOffset,
10529            float viewportToContentVerticalOffset) {
10530        final int minLine = mLayout.getLineForOffset(startIndex);
10531        final int maxLine = mLayout.getLineForOffset(endIndex - 1);
10532        for (int line = minLine; line <= maxLine; ++line) {
10533            final int lineStart = mLayout.getLineStart(line);
10534            final int lineEnd = mLayout.getLineEnd(line);
10535            final int offsetStart = Math.max(lineStart, startIndex);
10536            final int offsetEnd = Math.min(lineEnd, endIndex);
10537            final boolean ltrLine =
10538                    mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
10539            final float[] widths = new float[offsetEnd - offsetStart];
10540            mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths);
10541            final float top = mLayout.getLineTop(line);
10542            final float bottom = mLayout.getLineBottom(line);
10543            for (int offset = offsetStart; offset < offsetEnd; ++offset) {
10544                final float charWidth = widths[offset - offsetStart];
10545                final boolean isRtl = mLayout.isRtlCharAt(offset);
10546                final float primary = mLayout.getPrimaryHorizontal(offset);
10547                final float secondary = mLayout.getSecondaryHorizontal(offset);
10548                // TODO: This doesn't work perfectly for text with custom styles and
10549                // TAB chars.
10550                final float left;
10551                final float right;
10552                if (ltrLine) {
10553                    if (isRtl) {
10554                        left = secondary - charWidth;
10555                        right = secondary;
10556                    } else {
10557                        left = primary;
10558                        right = primary + charWidth;
10559                    }
10560                } else {
10561                    if (!isRtl) {
10562                        left = secondary;
10563                        right = secondary + charWidth;
10564                    } else {
10565                        left = primary - charWidth;
10566                        right = primary;
10567                    }
10568                }
10569                // TODO: Check top-right and bottom-left as well.
10570                final float localLeft = left + viewportToContentHorizontalOffset;
10571                final float localRight = right + viewportToContentHorizontalOffset;
10572                final float localTop = top + viewportToContentVerticalOffset;
10573                final float localBottom = bottom + viewportToContentVerticalOffset;
10574                final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
10575                final boolean isBottomRightVisible =
10576                        isPositionVisible(localRight, localBottom);
10577                int characterBoundsFlags = 0;
10578                if (isTopLeftVisible || isBottomRightVisible) {
10579                    characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
10580                }
10581                if (!isTopLeftVisible || !isBottomRightVisible) {
10582                    characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
10583                }
10584                if (isRtl) {
10585                    characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
10586                }
10587                // Here offset is the index in Java chars.
10588                builder.addCharacterBounds(offset, localLeft, localTop, localRight,
10589                        localBottom, characterBoundsFlags);
10590            }
10591        }
10592    }
10593
10594    /**
10595     * @hide
10596     */
10597    public boolean isPositionVisible(final float positionX, final float positionY) {
10598        synchronized (TEMP_POSITION) {
10599            final float[] position = TEMP_POSITION;
10600            position[0] = positionX;
10601            position[1] = positionY;
10602            View view = this;
10603
10604            while (view != null) {
10605                if (view != this) {
10606                    // Local scroll is already taken into account in positionX/Y
10607                    position[0] -= view.getScrollX();
10608                    position[1] -= view.getScrollY();
10609                }
10610
10611                if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
10612                        || position[1] > view.getHeight()) {
10613                    return false;
10614                }
10615
10616                if (!view.getMatrix().isIdentity()) {
10617                    view.getMatrix().mapPoints(position);
10618                }
10619
10620                position[0] += view.getLeft();
10621                position[1] += view.getTop();
10622
10623                final ViewParent parent = view.getParent();
10624                if (parent instanceof View) {
10625                    view = (View) parent;
10626                } else {
10627                    // We've reached the ViewRoot, stop iterating
10628                    view = null;
10629                }
10630            }
10631        }
10632
10633        // We've been able to walk up the view hierarchy and the position was never clipped
10634        return true;
10635    }
10636
10637    /**
10638     * Performs an accessibility action after it has been offered to the
10639     * delegate.
10640     *
10641     * @hide
10642     */
10643    @Override
10644    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
10645        if (mEditor != null
10646                && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
10647            return true;
10648        }
10649        switch (action) {
10650            case AccessibilityNodeInfo.ACTION_CLICK: {
10651                return performAccessibilityActionClick(arguments);
10652            }
10653            case AccessibilityNodeInfo.ACTION_COPY: {
10654                if (isFocused() && canCopy()) {
10655                    if (onTextContextMenuItem(ID_COPY)) {
10656                        return true;
10657                    }
10658                }
10659            } return false;
10660            case AccessibilityNodeInfo.ACTION_PASTE: {
10661                if (isFocused() && canPaste()) {
10662                    if (onTextContextMenuItem(ID_PASTE)) {
10663                        return true;
10664                    }
10665                }
10666            } return false;
10667            case AccessibilityNodeInfo.ACTION_CUT: {
10668                if (isFocused() && canCut()) {
10669                    if (onTextContextMenuItem(ID_CUT)) {
10670                        return true;
10671                    }
10672                }
10673            } return false;
10674            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
10675                ensureIterableTextForAccessibilitySelectable();
10676                CharSequence text = getIterableTextForAccessibility();
10677                if (text == null) {
10678                    return false;
10679                }
10680                final int start = (arguments != null) ? arguments.getInt(
10681                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
10682                final int end = (arguments != null) ? arguments.getInt(
10683                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
10684                if ((getSelectionStart() != start || getSelectionEnd() != end)) {
10685                    // No arguments clears the selection.
10686                    if (start == end && end == -1) {
10687                        Selection.removeSelection((Spannable) text);
10688                        return true;
10689                    }
10690                    if (start >= 0 && start <= end && end <= text.length()) {
10691                        Selection.setSelection((Spannable) text, start, end);
10692                        // Make sure selection mode is engaged.
10693                        if (mEditor != null) {
10694                            mEditor.startSelectionActionModeAsync(false);
10695                        }
10696                        return true;
10697                    }
10698                }
10699            } return false;
10700            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
10701            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
10702                ensureIterableTextForAccessibilitySelectable();
10703                return super.performAccessibilityActionInternal(action, arguments);
10704            }
10705            case ACCESSIBILITY_ACTION_SHARE: {
10706                if (isFocused() && canShare()) {
10707                    if (onTextContextMenuItem(ID_SHARE)) {
10708                        return true;
10709                    }
10710                }
10711            } return false;
10712            case AccessibilityNodeInfo.ACTION_SET_TEXT: {
10713                if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
10714                    return false;
10715                }
10716                CharSequence text = (arguments != null) ? arguments.getCharSequence(
10717                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
10718                setText(text);
10719                if (mText != null) {
10720                    int updatedTextLength = mText.length();
10721                    if (updatedTextLength > 0) {
10722                        Selection.setSelection((Spannable) mText, updatedTextLength);
10723                    }
10724                }
10725            } return true;
10726            default: {
10727                return super.performAccessibilityActionInternal(action, arguments);
10728            }
10729        }
10730    }
10731
10732    private boolean performAccessibilityActionClick(Bundle arguments) {
10733        boolean handled = false;
10734
10735        if (!isEnabled()) {
10736            return false;
10737        }
10738
10739        if (isClickable() || isLongClickable()) {
10740            // Simulate View.onTouchEvent for an ACTION_UP event
10741            if (isFocusable() && !isFocused()) {
10742                requestFocus();
10743            }
10744
10745            performClick();
10746            handled = true;
10747        }
10748
10749        // Show the IME, except when selecting in read-only text.
10750        if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
10751                && (isTextEditable() || isTextSelectable()) && isFocused()) {
10752            final InputMethodManager imm = InputMethodManager.peekInstance();
10753            viewClicked(imm);
10754            if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10755                handled |= imm.showSoftInput(this, 0);
10756            }
10757        }
10758
10759        return handled;
10760    }
10761
10762    private boolean hasSpannableText() {
10763        return mText != null && mText instanceof Spannable;
10764    }
10765
10766    /** @hide */
10767    @Override
10768    public void sendAccessibilityEventInternal(int eventType) {
10769        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
10770            mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
10771        }
10772
10773        super.sendAccessibilityEventInternal(eventType);
10774    }
10775
10776    @Override
10777    public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
10778        // Do not send scroll events since first they are not interesting for
10779        // accessibility and second such events a generated too frequently.
10780        // For details see the implementation of bringTextIntoView().
10781        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
10782            return;
10783        }
10784        super.sendAccessibilityEventUnchecked(event);
10785    }
10786
10787    /**
10788     * Returns the text that should be exposed to accessibility services.
10789     * <p>
10790     * This approximates what is displayed visually. If the user has specified
10791     * that accessibility services should speak passwords, this method will
10792     * bypass any password transformation method and return unobscured text.
10793     *
10794     * @return the text that should be exposed to accessibility services, may
10795     *         be {@code null} if no text is set
10796     */
10797    @Nullable
10798    private CharSequence getTextForAccessibility() {
10799        // If the text is empty, we must be showing the hint text.
10800        if (TextUtils.isEmpty(mText)) {
10801            return mHint;
10802        }
10803
10804        // Otherwise, return whatever text is being displayed.
10805        return TextUtils.trimToParcelableSize(mTransformed);
10806    }
10807
10808    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
10809            int fromIndex, int removedCount, int addedCount) {
10810        AccessibilityEvent event =
10811                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
10812        event.setFromIndex(fromIndex);
10813        event.setRemovedCount(removedCount);
10814        event.setAddedCount(addedCount);
10815        event.setBeforeText(beforeText);
10816        sendAccessibilityEventUnchecked(event);
10817    }
10818
10819    /**
10820     * Returns whether this text view is a current input method target.  The
10821     * default implementation just checks with {@link InputMethodManager}.
10822     * @return True if the TextView is a current input method target; false otherwise.
10823     */
10824    public boolean isInputMethodTarget() {
10825        InputMethodManager imm = InputMethodManager.peekInstance();
10826        return imm != null && imm.isActive(this);
10827    }
10828
10829    static final int ID_SELECT_ALL = android.R.id.selectAll;
10830    static final int ID_UNDO = android.R.id.undo;
10831    static final int ID_REDO = android.R.id.redo;
10832    static final int ID_CUT = android.R.id.cut;
10833    static final int ID_COPY = android.R.id.copy;
10834    static final int ID_PASTE = android.R.id.paste;
10835    static final int ID_SHARE = android.R.id.shareText;
10836    static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
10837    static final int ID_REPLACE = android.R.id.replaceText;
10838    static final int ID_ASSIST = android.R.id.textAssist;
10839    static final int ID_AUTOFILL = android.R.id.autofill;
10840
10841    /**
10842     * Called when a context menu option for the text view is selected.  Currently
10843     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
10844     * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
10845     *
10846     * @return true if the context menu item action was performed.
10847     */
10848    public boolean onTextContextMenuItem(int id) {
10849        int min = 0;
10850        int max = mText.length();
10851
10852        if (isFocused()) {
10853            final int selStart = getSelectionStart();
10854            final int selEnd = getSelectionEnd();
10855
10856            min = Math.max(0, Math.min(selStart, selEnd));
10857            max = Math.max(0, Math.max(selStart, selEnd));
10858        }
10859
10860        switch (id) {
10861            case ID_SELECT_ALL:
10862                final boolean hadSelection = hasSelection();
10863                selectAllText();
10864                if (mEditor != null && hadSelection) {
10865                    mEditor.invalidateActionModeAsync();
10866                }
10867                return true;
10868
10869            case ID_UNDO:
10870                if (mEditor != null) {
10871                    mEditor.undo();
10872                }
10873                return true;  // Returns true even if nothing was undone.
10874
10875            case ID_REDO:
10876                if (mEditor != null) {
10877                    mEditor.redo();
10878                }
10879                return true;  // Returns true even if nothing was undone.
10880
10881            case ID_PASTE:
10882                paste(min, max, true /* withFormatting */);
10883                return true;
10884
10885            case ID_PASTE_AS_PLAIN_TEXT:
10886                paste(min, max, false /* withFormatting */);
10887                return true;
10888
10889            case ID_CUT:
10890                final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
10891                if (setPrimaryClip(cutData)) {
10892                    deleteText_internal(min, max);
10893                } else {
10894                    Toast.makeText(getContext(),
10895                            com.android.internal.R.string.failed_to_copy_to_clipboard,
10896                            Toast.LENGTH_SHORT).show();
10897                }
10898                return true;
10899
10900            case ID_COPY:
10901                final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
10902                if (setPrimaryClip(copyData)) {
10903                    stopTextActionMode();
10904                } else {
10905                    Toast.makeText(getContext(),
10906                            com.android.internal.R.string.failed_to_copy_to_clipboard,
10907                            Toast.LENGTH_SHORT).show();
10908                }
10909                return true;
10910
10911            case ID_REPLACE:
10912                if (mEditor != null) {
10913                    mEditor.replace();
10914                }
10915                return true;
10916
10917            case ID_SHARE:
10918                shareSelectedText();
10919                return true;
10920
10921            case ID_AUTOFILL:
10922                requestAutofill();
10923                stopTextActionMode();
10924                return true;
10925        }
10926        return false;
10927    }
10928
10929    CharSequence getTransformedText(int start, int end) {
10930        return removeSuggestionSpans(mTransformed.subSequence(start, end));
10931    }
10932
10933    @Override
10934    public boolean performLongClick() {
10935        boolean handled = false;
10936
10937        if (mEditor != null) {
10938            mEditor.mIsBeingLongClicked = true;
10939        }
10940
10941        if (super.performLongClick()) {
10942            handled = true;
10943        }
10944
10945        if (mEditor != null) {
10946            handled |= mEditor.performLongClick(handled);
10947            mEditor.mIsBeingLongClicked = false;
10948        }
10949
10950        if (handled) {
10951            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
10952            if (mEditor != null) mEditor.mDiscardNextActionUp = true;
10953        } else {
10954            MetricsLogger.action(
10955                    mContext,
10956                    MetricsEvent.TEXT_LONGPRESS,
10957                    TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
10958        }
10959
10960        return handled;
10961    }
10962
10963    @Override
10964    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
10965        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
10966        if (mEditor != null) {
10967            mEditor.onScrollChanged();
10968        }
10969    }
10970
10971    /**
10972     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
10973     * by the IME or by the spell checker as the user types. This is done by adding
10974     * {@link SuggestionSpan}s to the text.
10975     *
10976     * When suggestions are enabled (default), this list of suggestions will be displayed when the
10977     * user asks for them on these parts of the text. This value depends on the inputType of this
10978     * TextView.
10979     *
10980     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
10981     *
10982     * In addition, the type variation must be one of
10983     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
10984     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
10985     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
10986     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
10987     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
10988     *
10989     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
10990     *
10991     * @return true if the suggestions popup window is enabled, based on the inputType.
10992     */
10993    public boolean isSuggestionsEnabled() {
10994        if (mEditor == null) return false;
10995        if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
10996            return false;
10997        }
10998        if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
10999
11000        final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
11001        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
11002                || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
11003                || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
11004                || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
11005                || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
11006    }
11007
11008    /**
11009     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
11010     * selection is initiated in this View.
11011     *
11012     * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
11013     * Paste, Replace and Share actions, depending on what this View supports.
11014     *
11015     * <p>A custom implementation can add new entries in the default menu in its
11016     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
11017     * method. The default actions can also be removed from the menu using
11018     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11019     * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
11020     * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
11021     *
11022     * <p>Returning false from
11023     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
11024     * will prevent the action mode from being started.
11025     *
11026     * <p>Action click events should be handled by the custom implementation of
11027     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
11028     * android.view.MenuItem)}.
11029     *
11030     * <p>Note that text selection mode is not started when a TextView receives focus and the
11031     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
11032     * that case, to allow for quick replacement.
11033     */
11034    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
11035        createEditorIfNeeded();
11036        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
11037    }
11038
11039    /**
11040     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
11041     *
11042     * @return The current custom selection callback.
11043     */
11044    public ActionMode.Callback getCustomSelectionActionModeCallback() {
11045        return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
11046    }
11047
11048    /**
11049     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
11050     * insertion is initiated in this View.
11051     * The standard implementation populates the menu with a subset of Select All,
11052     * Paste and Replace actions, depending on what this View supports.
11053     *
11054     * <p>A custom implementation can add new entries in the default menu in its
11055     * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
11056     * android.view.Menu)} method. The default actions can also be removed from the menu using
11057     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11058     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
11059     *
11060     * <p>Returning false from
11061     * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
11062     * android.view.Menu)} will prevent the action mode from being started.</p>
11063     *
11064     * <p>Action click events should be handled by the custom implementation of
11065     * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
11066     * android.view.MenuItem)}.</p>
11067     *
11068     * <p>Note that text insertion mode is not started when a TextView receives focus and the
11069     * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
11070     */
11071    public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
11072        createEditorIfNeeded();
11073        mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
11074    }
11075
11076    /**
11077     * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
11078     *
11079     * @return The current custom insertion callback.
11080     */
11081    public ActionMode.Callback getCustomInsertionActionModeCallback() {
11082        return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
11083    }
11084
11085    /**
11086     * Sets the {@link TextClassifier} for this TextView.
11087     */
11088    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
11089        mTextClassifier = textClassifier;
11090    }
11091
11092    /**
11093     * Returns the {@link TextClassifier} used by this TextView.
11094     * If no TextClassifier has been set, this TextView uses the default set by the
11095     * {@link TextClassificationManager}.
11096     */
11097    @NonNull
11098    public TextClassifier getTextClassifier() {
11099        if (mTextClassifier == null) {
11100            TextClassificationManager tcm =
11101                    mContext.getSystemService(TextClassificationManager.class);
11102            if (tcm != null) {
11103                mTextClassifier = tcm.getTextClassifier();
11104            } else {
11105                mTextClassifier = TextClassifier.NO_OP;
11106            }
11107        }
11108        return mTextClassifier;
11109    }
11110
11111    /**
11112     * @hide
11113     */
11114    protected void stopTextActionMode() {
11115        if (mEditor != null) {
11116            mEditor.stopTextActionMode();
11117        }
11118    }
11119
11120    boolean canUndo() {
11121        return mEditor != null && mEditor.canUndo();
11122    }
11123
11124    boolean canRedo() {
11125        return mEditor != null && mEditor.canRedo();
11126    }
11127
11128    boolean canCut() {
11129        if (hasPasswordTransformationMethod()) {
11130            return false;
11131        }
11132
11133        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
11134                && mEditor.mKeyListener != null) {
11135            return true;
11136        }
11137
11138        return false;
11139    }
11140
11141    boolean canCopy() {
11142        if (hasPasswordTransformationMethod()) {
11143            return false;
11144        }
11145
11146        if (mText.length() > 0 && hasSelection() && mEditor != null) {
11147            return true;
11148        }
11149
11150        return false;
11151    }
11152
11153    boolean canShare() {
11154        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
11155            return false;
11156        }
11157        return canCopy();
11158    }
11159
11160    boolean isDeviceProvisioned() {
11161        if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
11162            mDeviceProvisionedState = Settings.Global.getInt(
11163                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
11164                    ? DEVICE_PROVISIONED_YES
11165                    : DEVICE_PROVISIONED_NO;
11166        }
11167        return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
11168    }
11169
11170    boolean canPaste() {
11171        return (mText instanceof Editable
11172                && mEditor != null && mEditor.mKeyListener != null
11173                && getSelectionStart() >= 0
11174                && getSelectionEnd() >= 0
11175                && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11176                        .hasPrimaryClip());
11177    }
11178
11179    boolean canPasteAsPlainText() {
11180        if (!canPaste()) {
11181            return false;
11182        }
11183
11184        final ClipData clipData =
11185                ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11186                        .getPrimaryClip();
11187        final ClipDescription description = clipData.getDescription();
11188        final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
11189        final CharSequence text = clipData.getItemAt(0).getText();
11190        if (isPlainType && (text instanceof Spanned)) {
11191            Spanned spanned = (Spanned) text;
11192            if (TextUtils.hasStyleSpan(spanned)) {
11193                return true;
11194            }
11195        }
11196        return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
11197    }
11198
11199    boolean canProcessText() {
11200        if (getId() == View.NO_ID) {
11201            return false;
11202        }
11203        return canShare();
11204    }
11205
11206    boolean canSelectAllText() {
11207        return canSelectText() && !hasPasswordTransformationMethod()
11208                && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
11209    }
11210
11211    boolean selectAllText() {
11212        if (mEditor != null) {
11213            // Hide the toolbar before changing the selection to avoid flickering.
11214            mEditor.hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
11215        }
11216        final int length = mText.length();
11217        Selection.setSelection((Spannable) mText, 0, length);
11218        return length > 0;
11219    }
11220
11221    void replaceSelectionWithText(CharSequence text) {
11222        ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
11223    }
11224
11225    /**
11226     * Paste clipboard content between min and max positions.
11227     */
11228    private void paste(int min, int max, boolean withFormatting) {
11229        ClipboardManager clipboard =
11230                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11231        ClipData clip = clipboard.getPrimaryClip();
11232        if (clip != null) {
11233            boolean didFirst = false;
11234            for (int i = 0; i < clip.getItemCount(); i++) {
11235                final CharSequence paste;
11236                if (withFormatting) {
11237                    paste = clip.getItemAt(i).coerceToStyledText(getContext());
11238                } else {
11239                    // Get an item as text and remove all spans by toString().
11240                    final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
11241                    paste = (text instanceof Spanned) ? text.toString() : text;
11242                }
11243                if (paste != null) {
11244                    if (!didFirst) {
11245                        Selection.setSelection((Spannable) mText, max);
11246                        ((Editable) mText).replace(min, max, paste);
11247                        didFirst = true;
11248                    } else {
11249                        ((Editable) mText).insert(getSelectionEnd(), "\n");
11250                        ((Editable) mText).insert(getSelectionEnd(), paste);
11251                    }
11252                }
11253            }
11254            sLastCutCopyOrTextChangedTime = 0;
11255        }
11256    }
11257
11258    private void shareSelectedText() {
11259        String selectedText = getSelectedText();
11260        if (selectedText != null && !selectedText.isEmpty()) {
11261            Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
11262            sharingIntent.setType("text/plain");
11263            sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
11264            selectedText = TextUtils.trimToParcelableSize(selectedText);
11265            sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
11266            getContext().startActivity(Intent.createChooser(sharingIntent, null));
11267            Selection.setSelection((Spannable) mText, getSelectionEnd());
11268        }
11269    }
11270
11271    @CheckResult
11272    private boolean setPrimaryClip(ClipData clip) {
11273        ClipboardManager clipboard =
11274                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11275        try {
11276            clipboard.setPrimaryClip(clip);
11277        } catch (Throwable t) {
11278            return false;
11279        }
11280        sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
11281        return true;
11282    }
11283
11284    /**
11285     * Get the character offset closest to the specified absolute position. A typical use case is to
11286     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
11287     *
11288     * @param x The horizontal absolute position of a point on screen
11289     * @param y The vertical absolute position of a point on screen
11290     * @return the character offset for the character whose position is closest to the specified
11291     *  position. Returns -1 if there is no layout.
11292     */
11293    public int getOffsetForPosition(float x, float y) {
11294        if (getLayout() == null) return -1;
11295        final int line = getLineAtCoordinate(y);
11296        final int offset = getOffsetAtCoordinate(line, x);
11297        return offset;
11298    }
11299
11300    float convertToLocalHorizontalCoordinate(float x) {
11301        x -= getTotalPaddingLeft();
11302        // Clamp the position to inside of the view.
11303        x = Math.max(0.0f, x);
11304        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
11305        x += getScrollX();
11306        return x;
11307    }
11308
11309    int getLineAtCoordinate(float y) {
11310        y -= getTotalPaddingTop();
11311        // Clamp the position to inside of the view.
11312        y = Math.max(0.0f, y);
11313        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
11314        y += getScrollY();
11315        return getLayout().getLineForVertical((int) y);
11316    }
11317
11318    int getLineAtCoordinateUnclamped(float y) {
11319        y -= getTotalPaddingTop();
11320        y += getScrollY();
11321        return getLayout().getLineForVertical((int) y);
11322    }
11323
11324    int getOffsetAtCoordinate(int line, float x) {
11325        x = convertToLocalHorizontalCoordinate(x);
11326        return getLayout().getOffsetForHorizontal(line, x);
11327    }
11328
11329    @Override
11330    public boolean onDragEvent(DragEvent event) {
11331        switch (event.getAction()) {
11332            case DragEvent.ACTION_DRAG_STARTED:
11333                return mEditor != null && mEditor.hasInsertionController();
11334
11335            case DragEvent.ACTION_DRAG_ENTERED:
11336                TextView.this.requestFocus();
11337                return true;
11338
11339            case DragEvent.ACTION_DRAG_LOCATION:
11340                if (mText instanceof Spannable) {
11341                    final int offset = getOffsetForPosition(event.getX(), event.getY());
11342                    Selection.setSelection((Spannable) mText, offset);
11343                }
11344                return true;
11345
11346            case DragEvent.ACTION_DROP:
11347                if (mEditor != null) mEditor.onDrop(event);
11348                return true;
11349
11350            case DragEvent.ACTION_DRAG_ENDED:
11351            case DragEvent.ACTION_DRAG_EXITED:
11352            default:
11353                return true;
11354        }
11355    }
11356
11357    boolean isInBatchEditMode() {
11358        if (mEditor == null) return false;
11359        final Editor.InputMethodState ims = mEditor.mInputMethodState;
11360        if (ims != null) {
11361            return ims.mBatchEditNesting > 0;
11362        }
11363        return mEditor.mInBatchEditControllers;
11364    }
11365
11366    @Override
11367    public void onRtlPropertiesChanged(int layoutDirection) {
11368        super.onRtlPropertiesChanged(layoutDirection);
11369
11370        final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
11371        if (mTextDir != newTextDir) {
11372            mTextDir = newTextDir;
11373            if (mLayout != null) {
11374                checkForRelayout();
11375            }
11376        }
11377    }
11378
11379    /**
11380     * @hide
11381     */
11382    protected TextDirectionHeuristic getTextDirectionHeuristic() {
11383        if (hasPasswordTransformationMethod()) {
11384            // passwords fields should be LTR
11385            return TextDirectionHeuristics.LTR;
11386        }
11387
11388        if (mEditor != null
11389                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
11390                    == EditorInfo.TYPE_CLASS_PHONE) {
11391            // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
11392            // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
11393            // RTL digits.
11394            final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
11395            final String zero = symbols.getDigitStrings()[0];
11396            // In case the zero digit is multi-codepoint, just use the first codepoint to determine
11397            // direction.
11398            final int firstCodepoint = zero.codePointAt(0);
11399            final byte digitDirection = Character.getDirectionality(firstCodepoint);
11400            if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
11401                    || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
11402                return TextDirectionHeuristics.RTL;
11403            } else {
11404                return TextDirectionHeuristics.LTR;
11405            }
11406        }
11407
11408        // Always need to resolve layout direction first
11409        final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
11410
11411        // Now, we can select the heuristic
11412        switch (getTextDirection()) {
11413            default:
11414            case TEXT_DIRECTION_FIRST_STRONG:
11415                return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11416                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
11417            case TEXT_DIRECTION_ANY_RTL:
11418                return TextDirectionHeuristics.ANYRTL_LTR;
11419            case TEXT_DIRECTION_LTR:
11420                return TextDirectionHeuristics.LTR;
11421            case TEXT_DIRECTION_RTL:
11422                return TextDirectionHeuristics.RTL;
11423            case TEXT_DIRECTION_LOCALE:
11424                return TextDirectionHeuristics.LOCALE;
11425            case TEXT_DIRECTION_FIRST_STRONG_LTR:
11426                return TextDirectionHeuristics.FIRSTSTRONG_LTR;
11427            case TEXT_DIRECTION_FIRST_STRONG_RTL:
11428                return TextDirectionHeuristics.FIRSTSTRONG_RTL;
11429        }
11430    }
11431
11432    /**
11433     * @hide
11434     */
11435    @Override
11436    public void onResolveDrawables(int layoutDirection) {
11437        // No need to resolve twice
11438        if (mLastLayoutDirection == layoutDirection) {
11439            return;
11440        }
11441        mLastLayoutDirection = layoutDirection;
11442
11443        // Resolve drawables
11444        if (mDrawables != null) {
11445            if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
11446                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
11447                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
11448                applyCompoundDrawableTint();
11449            }
11450        }
11451    }
11452
11453    /**
11454     * Prepares a drawable for display by propagating layout direction and
11455     * drawable state.
11456     *
11457     * @param dr the drawable to prepare
11458     */
11459    private void prepareDrawableForDisplay(@Nullable Drawable dr) {
11460        if (dr == null) {
11461            return;
11462        }
11463
11464        dr.setLayoutDirection(getLayoutDirection());
11465
11466        if (dr.isStateful()) {
11467            dr.setState(getDrawableState());
11468            dr.jumpToCurrentState();
11469        }
11470    }
11471
11472    /**
11473     * @hide
11474     */
11475    protected void resetResolvedDrawables() {
11476        super.resetResolvedDrawables();
11477        mLastLayoutDirection = -1;
11478    }
11479
11480    /**
11481     * @hide
11482     */
11483    protected void viewClicked(InputMethodManager imm) {
11484        if (imm != null) {
11485            imm.viewClicked(this);
11486        }
11487    }
11488
11489    /**
11490     * Deletes the range of text [start, end[.
11491     * @hide
11492     */
11493    protected void deleteText_internal(int start, int end) {
11494        ((Editable) mText).delete(start, end);
11495    }
11496
11497    /**
11498     * Replaces the range of text [start, end[ by replacement text
11499     * @hide
11500     */
11501    protected void replaceText_internal(int start, int end, CharSequence text) {
11502        ((Editable) mText).replace(start, end, text);
11503    }
11504
11505    /**
11506     * Sets a span on the specified range of text
11507     * @hide
11508     */
11509    protected void setSpan_internal(Object span, int start, int end, int flags) {
11510        ((Editable) mText).setSpan(span, start, end, flags);
11511    }
11512
11513    /**
11514     * Moves the cursor to the specified offset position in text
11515     * @hide
11516     */
11517    protected void setCursorPosition_internal(int start, int end) {
11518        Selection.setSelection(((Editable) mText), start, end);
11519    }
11520
11521    /**
11522     * An Editor should be created as soon as any of the editable-specific fields (grouped
11523     * inside the Editor object) is assigned to a non-default value.
11524     * This method will create the Editor if needed.
11525     *
11526     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
11527     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
11528     * Editor for backward compatibility, as soon as one of these fields is assigned.
11529     *
11530     * Also note that for performance reasons, the mEditor is created when needed, but not
11531     * reset when no more edit-specific fields are needed.
11532     */
11533    private void createEditorIfNeeded() {
11534        if (mEditor == null) {
11535            mEditor = new Editor(this);
11536        }
11537    }
11538
11539    /**
11540     * @hide
11541     */
11542    @Override
11543    public CharSequence getIterableTextForAccessibility() {
11544        return mText;
11545    }
11546
11547    private void ensureIterableTextForAccessibilitySelectable() {
11548        if (!(mText instanceof Spannable)) {
11549            setText(mText, BufferType.SPANNABLE);
11550        }
11551    }
11552
11553    /**
11554     * @hide
11555     */
11556    @Override
11557    public TextSegmentIterator getIteratorForGranularity(int granularity) {
11558        switch (granularity) {
11559            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
11560                Spannable text = (Spannable) getIterableTextForAccessibility();
11561                if (!TextUtils.isEmpty(text) && getLayout() != null) {
11562                    AccessibilityIterators.LineTextSegmentIterator iterator =
11563                            AccessibilityIterators.LineTextSegmentIterator.getInstance();
11564                    iterator.initialize(text, getLayout());
11565                    return iterator;
11566                }
11567            } break;
11568            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
11569                Spannable text = (Spannable) getIterableTextForAccessibility();
11570                if (!TextUtils.isEmpty(text) && getLayout() != null) {
11571                    AccessibilityIterators.PageTextSegmentIterator iterator =
11572                            AccessibilityIterators.PageTextSegmentIterator.getInstance();
11573                    iterator.initialize(this);
11574                    return iterator;
11575                }
11576            } break;
11577        }
11578        return super.getIteratorForGranularity(granularity);
11579    }
11580
11581    /**
11582     * @hide
11583     */
11584    @Override
11585    public int getAccessibilitySelectionStart() {
11586        return getSelectionStart();
11587    }
11588
11589    /**
11590     * @hide
11591     */
11592    public boolean isAccessibilitySelectionExtendable() {
11593        return true;
11594    }
11595
11596    /**
11597     * @hide
11598     */
11599    @Override
11600    public int getAccessibilitySelectionEnd() {
11601        return getSelectionEnd();
11602    }
11603
11604    /**
11605     * @hide
11606     */
11607    @Override
11608    public void setAccessibilitySelection(int start, int end) {
11609        if (getAccessibilitySelectionStart() == start
11610                && getAccessibilitySelectionEnd() == end) {
11611            return;
11612        }
11613        CharSequence text = getIterableTextForAccessibility();
11614        if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
11615            Selection.setSelection((Spannable) text, start, end);
11616        } else {
11617            Selection.removeSelection((Spannable) text);
11618        }
11619        // Hide all selection controllers used for adjusting selection
11620        // since we are doing so explicitlty by other means and these
11621        // controllers interact with how selection behaves.
11622        if (mEditor != null) {
11623            mEditor.hideCursorAndSpanControllers();
11624            mEditor.stopTextActionMode();
11625        }
11626    }
11627
11628    /** @hide */
11629    @Override
11630    protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
11631        super.encodeProperties(stream);
11632
11633        TruncateAt ellipsize = getEllipsize();
11634        stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
11635        stream.addProperty("text:textSize", getTextSize());
11636        stream.addProperty("text:scaledTextSize", getScaledTextSize());
11637        stream.addProperty("text:typefaceStyle", getTypefaceStyle());
11638        stream.addProperty("text:selectionStart", getSelectionStart());
11639        stream.addProperty("text:selectionEnd", getSelectionEnd());
11640        stream.addProperty("text:curTextColor", mCurTextColor);
11641        stream.addProperty("text:text", mText == null ? null : mText.toString());
11642        stream.addProperty("text:gravity", mGravity);
11643    }
11644
11645    /**
11646     * User interface state that is stored by TextView for implementing
11647     * {@link View#onSaveInstanceState}.
11648     */
11649    public static class SavedState extends BaseSavedState {
11650        int selStart = -1;
11651        int selEnd = -1;
11652        CharSequence text;
11653        boolean frozenWithFocus;
11654        CharSequence error;
11655        ParcelableParcel editorState;  // Optional state from Editor.
11656
11657        SavedState(Parcelable superState) {
11658            super(superState);
11659        }
11660
11661        @Override
11662        public void writeToParcel(Parcel out, int flags) {
11663            super.writeToParcel(out, flags);
11664            out.writeInt(selStart);
11665            out.writeInt(selEnd);
11666            out.writeInt(frozenWithFocus ? 1 : 0);
11667            TextUtils.writeToParcel(text, out, flags);
11668
11669            if (error == null) {
11670                out.writeInt(0);
11671            } else {
11672                out.writeInt(1);
11673                TextUtils.writeToParcel(error, out, flags);
11674            }
11675
11676            if (editorState == null) {
11677                out.writeInt(0);
11678            } else {
11679                out.writeInt(1);
11680                editorState.writeToParcel(out, flags);
11681            }
11682        }
11683
11684        @Override
11685        public String toString() {
11686            String str = "TextView.SavedState{"
11687                    + Integer.toHexString(System.identityHashCode(this))
11688                    + " start=" + selStart + " end=" + selEnd;
11689            if (text != null) {
11690                str += " text=" + text;
11691            }
11692            return str + "}";
11693        }
11694
11695        @SuppressWarnings("hiding")
11696        public static final Parcelable.Creator<SavedState> CREATOR =
11697                new Parcelable.Creator<SavedState>() {
11698                    public SavedState createFromParcel(Parcel in) {
11699                        return new SavedState(in);
11700                    }
11701
11702                    public SavedState[] newArray(int size) {
11703                        return new SavedState[size];
11704                    }
11705                };
11706
11707        private SavedState(Parcel in) {
11708            super(in);
11709            selStart = in.readInt();
11710            selEnd = in.readInt();
11711            frozenWithFocus = (in.readInt() != 0);
11712            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11713
11714            if (in.readInt() != 0) {
11715                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11716            }
11717
11718            if (in.readInt() != 0) {
11719                editorState = ParcelableParcel.CREATOR.createFromParcel(in);
11720            }
11721        }
11722    }
11723
11724    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
11725        private char[] mChars;
11726        private int mStart, mLength;
11727
11728        public CharWrapper(char[] chars, int start, int len) {
11729            mChars = chars;
11730            mStart = start;
11731            mLength = len;
11732        }
11733
11734        /* package */ void set(char[] chars, int start, int len) {
11735            mChars = chars;
11736            mStart = start;
11737            mLength = len;
11738        }
11739
11740        public int length() {
11741            return mLength;
11742        }
11743
11744        public char charAt(int off) {
11745            return mChars[off + mStart];
11746        }
11747
11748        @Override
11749        public String toString() {
11750            return new String(mChars, mStart, mLength);
11751        }
11752
11753        public CharSequence subSequence(int start, int end) {
11754            if (start < 0 || end < 0 || start > mLength || end > mLength) {
11755                throw new IndexOutOfBoundsException(start + ", " + end);
11756            }
11757
11758            return new String(mChars, start + mStart, end - start);
11759        }
11760
11761        public void getChars(int start, int end, char[] buf, int off) {
11762            if (start < 0 || end < 0 || start > mLength || end > mLength) {
11763                throw new IndexOutOfBoundsException(start + ", " + end);
11764            }
11765
11766            System.arraycopy(mChars, start + mStart, buf, off, end - start);
11767        }
11768
11769        @Override
11770        public void drawText(BaseCanvas c, int start, int end,
11771                             float x, float y, Paint p) {
11772            c.drawText(mChars, start + mStart, end - start, x, y, p);
11773        }
11774
11775        @Override
11776        public void drawTextRun(BaseCanvas c, int start, int end,
11777                int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
11778            int count = end - start;
11779            int contextCount = contextEnd - contextStart;
11780            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
11781                    contextCount, x, y, isRtl, p);
11782        }
11783
11784        public float measureText(int start, int end, Paint p) {
11785            return p.measureText(mChars, start + mStart, end - start);
11786        }
11787
11788        public int getTextWidths(int start, int end, float[] widths, Paint p) {
11789            return p.getTextWidths(mChars, start + mStart, end - start, widths);
11790        }
11791
11792        public float getTextRunAdvances(int start, int end, int contextStart,
11793                int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
11794                Paint p) {
11795            int count = end - start;
11796            int contextCount = contextEnd - contextStart;
11797            return p.getTextRunAdvances(mChars, start + mStart, count,
11798                    contextStart + mStart, contextCount, isRtl, advances,
11799                    advancesIndex);
11800        }
11801
11802        public int getTextRunCursor(int contextStart, int contextEnd, int dir,
11803                int offset, int cursorOpt, Paint p) {
11804            int contextCount = contextEnd - contextStart;
11805            return p.getTextRunCursor(mChars, contextStart + mStart,
11806                    contextCount, dir, offset + mStart, cursorOpt);
11807        }
11808    }
11809
11810    private static final class Marquee {
11811        // TODO: Add an option to configure this
11812        private static final float MARQUEE_DELTA_MAX = 0.07f;
11813        private static final int MARQUEE_DELAY = 1200;
11814        private static final int MARQUEE_DP_PER_SECOND = 30;
11815
11816        private static final byte MARQUEE_STOPPED = 0x0;
11817        private static final byte MARQUEE_STARTING = 0x1;
11818        private static final byte MARQUEE_RUNNING = 0x2;
11819
11820        private final WeakReference<TextView> mView;
11821        private final Choreographer mChoreographer;
11822
11823        private byte mStatus = MARQUEE_STOPPED;
11824        private final float mPixelsPerSecond;
11825        private float mMaxScroll;
11826        private float mMaxFadeScroll;
11827        private float mGhostStart;
11828        private float mGhostOffset;
11829        private float mFadeStop;
11830        private int mRepeatLimit;
11831
11832        private float mScroll;
11833        private long mLastAnimationMs;
11834
11835        Marquee(TextView v) {
11836            final float density = v.getContext().getResources().getDisplayMetrics().density;
11837            mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
11838            mView = new WeakReference<TextView>(v);
11839            mChoreographer = Choreographer.getInstance();
11840        }
11841
11842        private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
11843            @Override
11844            public void doFrame(long frameTimeNanos) {
11845                tick();
11846            }
11847        };
11848
11849        private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
11850            @Override
11851            public void doFrame(long frameTimeNanos) {
11852                mStatus = MARQUEE_RUNNING;
11853                mLastAnimationMs = mChoreographer.getFrameTime();
11854                tick();
11855            }
11856        };
11857
11858        private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
11859            @Override
11860            public void doFrame(long frameTimeNanos) {
11861                if (mStatus == MARQUEE_RUNNING) {
11862                    if (mRepeatLimit >= 0) {
11863                        mRepeatLimit--;
11864                    }
11865                    start(mRepeatLimit);
11866                }
11867            }
11868        };
11869
11870        void tick() {
11871            if (mStatus != MARQUEE_RUNNING) {
11872                return;
11873            }
11874
11875            mChoreographer.removeFrameCallback(mTickCallback);
11876
11877            final TextView textView = mView.get();
11878            if (textView != null && (textView.isFocused() || textView.isSelected())) {
11879                long currentMs = mChoreographer.getFrameTime();
11880                long deltaMs = currentMs - mLastAnimationMs;
11881                mLastAnimationMs = currentMs;
11882                float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
11883                mScroll += deltaPx;
11884                if (mScroll > mMaxScroll) {
11885                    mScroll = mMaxScroll;
11886                    mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
11887                } else {
11888                    mChoreographer.postFrameCallback(mTickCallback);
11889                }
11890                textView.invalidate();
11891            }
11892        }
11893
11894        void stop() {
11895            mStatus = MARQUEE_STOPPED;
11896            mChoreographer.removeFrameCallback(mStartCallback);
11897            mChoreographer.removeFrameCallback(mRestartCallback);
11898            mChoreographer.removeFrameCallback(mTickCallback);
11899            resetScroll();
11900        }
11901
11902        private void resetScroll() {
11903            mScroll = 0.0f;
11904            final TextView textView = mView.get();
11905            if (textView != null) textView.invalidate();
11906        }
11907
11908        void start(int repeatLimit) {
11909            if (repeatLimit == 0) {
11910                stop();
11911                return;
11912            }
11913            mRepeatLimit = repeatLimit;
11914            final TextView textView = mView.get();
11915            if (textView != null && textView.mLayout != null) {
11916                mStatus = MARQUEE_STARTING;
11917                mScroll = 0.0f;
11918                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
11919                        - textView.getCompoundPaddingRight();
11920                final float lineWidth = textView.mLayout.getLineWidth(0);
11921                final float gap = textWidth / 3.0f;
11922                mGhostStart = lineWidth - textWidth + gap;
11923                mMaxScroll = mGhostStart + textWidth;
11924                mGhostOffset = lineWidth + gap;
11925                mFadeStop = lineWidth + textWidth / 6.0f;
11926                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
11927
11928                textView.invalidate();
11929                mChoreographer.postFrameCallback(mStartCallback);
11930            }
11931        }
11932
11933        float getGhostOffset() {
11934            return mGhostOffset;
11935        }
11936
11937        float getScroll() {
11938            return mScroll;
11939        }
11940
11941        float getMaxFadeScroll() {
11942            return mMaxFadeScroll;
11943        }
11944
11945        boolean shouldDrawLeftFade() {
11946            return mScroll <= mFadeStop;
11947        }
11948
11949        boolean shouldDrawGhost() {
11950            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
11951        }
11952
11953        boolean isRunning() {
11954            return mStatus == MARQUEE_RUNNING;
11955        }
11956
11957        boolean isStopped() {
11958            return mStatus == MARQUEE_STOPPED;
11959        }
11960    }
11961
11962    private class ChangeWatcher implements TextWatcher, SpanWatcher {
11963
11964        private CharSequence mBeforeText;
11965
11966        public void beforeTextChanged(CharSequence buffer, int start,
11967                                      int before, int after) {
11968            if (DEBUG_EXTRACT) {
11969                Log.v(LOG_TAG, "beforeTextChanged start=" + start
11970                        + " before=" + before + " after=" + after + ": " + buffer);
11971            }
11972
11973            if (AccessibilityManager.getInstance(mContext).isEnabled()
11974                    && !isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) {
11975                mBeforeText = buffer.toString();
11976            }
11977
11978            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
11979        }
11980
11981        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
11982            if (DEBUG_EXTRACT) {
11983                Log.v(LOG_TAG, "onTextChanged start=" + start
11984                        + " before=" + before + " after=" + after + ": " + buffer);
11985            }
11986            TextView.this.handleTextChanged(buffer, start, before, after);
11987
11988            if (AccessibilityManager.getInstance(mContext).isEnabled()
11989                    && (isFocused() || isSelected() && isShown())) {
11990                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
11991                mBeforeText = null;
11992            }
11993        }
11994
11995        public void afterTextChanged(Editable buffer) {
11996            if (DEBUG_EXTRACT) {
11997                Log.v(LOG_TAG, "afterTextChanged: " + buffer);
11998            }
11999            TextView.this.sendAfterTextChanged(buffer);
12000
12001            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
12002                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
12003            }
12004        }
12005
12006        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
12007            if (DEBUG_EXTRACT) {
12008                Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
12009                        + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
12010            }
12011            TextView.this.spanChange(buf, what, s, st, e, en);
12012        }
12013
12014        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
12015            if (DEBUG_EXTRACT) {
12016                Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
12017            }
12018            TextView.this.spanChange(buf, what, -1, s, -1, e);
12019        }
12020
12021        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
12022            if (DEBUG_EXTRACT) {
12023                Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
12024            }
12025            TextView.this.spanChange(buf, what, s, -1, e, -1);
12026        }
12027    }
12028}
12029