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