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