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