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