TextView.java revision 6290fb599aab2ec12f065155c3d0c634c7a3fb84
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            notifyAccessibilityStateChanged(
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            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
5156        }
5157    }
5158
5159    /**
5160     * Convenience method to append the specified text to the TextView's
5161     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5162     * if it was not already editable.
5163     *
5164     * @param text text to be appended to the already displayed text
5165     */
5166    public final void append(CharSequence text) {
5167        append(text, 0, text.length());
5168    }
5169
5170    /**
5171     * Convenience method to append the specified text slice to the TextView's
5172     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
5173     * if it was not already editable.
5174     *
5175     * @param text text to be appended to the already displayed text
5176     * @param start the index of the first character in the {@code text}
5177     * @param end the index of the character following the last character in the {@code text}
5178     *
5179     * @see Appendable#append(CharSequence, int, int)
5180     */
5181    public void append(CharSequence text, int start, int end) {
5182        if (!(mText instanceof Editable)) {
5183            setText(mText, BufferType.EDITABLE);
5184        }
5185
5186        ((Editable) mText).append(text, start, end);
5187
5188        if (mAutoLinkMask != 0) {
5189            boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
5190            // Do not change the movement method for text that support text selection as it
5191            // would prevent an arbitrary cursor displacement.
5192            if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
5193                setMovementMethod(LinkMovementMethod.getInstance());
5194            }
5195        }
5196    }
5197
5198    private void updateTextColors() {
5199        boolean inval = false;
5200        final int[] drawableState = getDrawableState();
5201        int color = mTextColor.getColorForState(drawableState, 0);
5202        if (color != mCurTextColor) {
5203            mCurTextColor = color;
5204            inval = true;
5205        }
5206        if (mLinkTextColor != null) {
5207            color = mLinkTextColor.getColorForState(drawableState, 0);
5208            if (color != mTextPaint.linkColor) {
5209                mTextPaint.linkColor = color;
5210                inval = true;
5211            }
5212        }
5213        if (mHintTextColor != null) {
5214            color = mHintTextColor.getColorForState(drawableState, 0);
5215            if (color != mCurHintTextColor) {
5216                mCurHintTextColor = color;
5217                if (mText.length() == 0) {
5218                    inval = true;
5219                }
5220            }
5221        }
5222        if (inval) {
5223            // Text needs to be redrawn with the new color
5224            if (mEditor != null) mEditor.invalidateTextDisplayList();
5225            invalidate();
5226        }
5227    }
5228
5229    @Override
5230    protected void drawableStateChanged() {
5231        super.drawableStateChanged();
5232
5233        if (mTextColor != null && mTextColor.isStateful()
5234                || (mHintTextColor != null && mHintTextColor.isStateful())
5235                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
5236            updateTextColors();
5237        }
5238
5239        if (mDrawables != null) {
5240            final int[] state = getDrawableState();
5241            for (Drawable dr : mDrawables.mShowing) {
5242                if (dr != null && dr.isStateful() && dr.setState(state)) {
5243                    invalidateDrawable(dr);
5244                }
5245            }
5246        }
5247    }
5248
5249    @Override
5250    public void drawableHotspotChanged(float x, float y) {
5251        super.drawableHotspotChanged(x, y);
5252
5253        if (mDrawables != null) {
5254            for (Drawable dr : mDrawables.mShowing) {
5255                if (dr != null) {
5256                    dr.setHotspot(x, y);
5257                }
5258            }
5259        }
5260    }
5261
5262    @Override
5263    public Parcelable onSaveInstanceState() {
5264        Parcelable superState = super.onSaveInstanceState();
5265
5266        // Save state if we are forced to
5267        final boolean freezesText = getFreezesText();
5268        boolean hasSelection = false;
5269        int start = -1;
5270        int end = -1;
5271
5272        if (mText != null) {
5273            start = getSelectionStart();
5274            end = getSelectionEnd();
5275            if (start >= 0 || end >= 0) {
5276                // Or save state if there is a selection
5277                hasSelection = true;
5278            }
5279        }
5280
5281        if (freezesText || hasSelection) {
5282            SavedState ss = new SavedState(superState);
5283
5284            if (freezesText) {
5285                if (mText instanceof Spanned) {
5286                    final Spannable sp = new SpannableStringBuilder(mText);
5287
5288                    if (mEditor != null) {
5289                        removeMisspelledSpans(sp);
5290                        sp.removeSpan(mEditor.mSuggestionRangeSpan);
5291                    }
5292
5293                    ss.text = sp;
5294                } else {
5295                    ss.text = mText.toString();
5296                }
5297            }
5298
5299            if (hasSelection) {
5300                // XXX Should also save the current scroll position!
5301                ss.selStart = start;
5302                ss.selEnd = end;
5303            }
5304
5305            if (isFocused() && start >= 0 && end >= 0) {
5306                ss.frozenWithFocus = true;
5307            }
5308
5309            ss.error = getError();
5310
5311            if (mEditor != null) {
5312                ss.editorState = mEditor.saveInstanceState();
5313            }
5314            return ss;
5315        }
5316
5317        return superState;
5318    }
5319
5320    void removeMisspelledSpans(Spannable spannable) {
5321        SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5322                SuggestionSpan.class);
5323        for (int i = 0; i < suggestionSpans.length; i++) {
5324            int flags = suggestionSpans[i].getFlags();
5325            if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5326                    && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5327                spannable.removeSpan(suggestionSpans[i]);
5328            }
5329        }
5330    }
5331
5332    @Override
5333    public void onRestoreInstanceState(Parcelable state) {
5334        if (!(state instanceof SavedState)) {
5335            super.onRestoreInstanceState(state);
5336            return;
5337        }
5338
5339        SavedState ss = (SavedState) state;
5340        super.onRestoreInstanceState(ss.getSuperState());
5341
5342        // XXX restore buffer type too, as well as lots of other stuff
5343        if (ss.text != null) {
5344            setText(ss.text);
5345        }
5346
5347        if (ss.selStart >= 0 && ss.selEnd >= 0) {
5348            if (mText instanceof Spannable) {
5349                int len = mText.length();
5350
5351                if (ss.selStart > len || ss.selEnd > len) {
5352                    String restored = "";
5353
5354                    if (ss.text != null) {
5355                        restored = "(restored) ";
5356                    }
5357
5358                    Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5359                            + " out of range for " + restored + "text " + mText);
5360                } else {
5361                    Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
5362
5363                    if (ss.frozenWithFocus) {
5364                        createEditorIfNeeded();
5365                        mEditor.mFrozenWithFocus = true;
5366                    }
5367                }
5368            }
5369        }
5370
5371        if (ss.error != null) {
5372            final CharSequence error = ss.error;
5373            // Display the error later, after the first layout pass
5374            post(new Runnable() {
5375                public void run() {
5376                    if (mEditor == null || !mEditor.mErrorWasChanged) {
5377                        setError(error);
5378                    }
5379                }
5380            });
5381        }
5382
5383        if (ss.editorState != null) {
5384            createEditorIfNeeded();
5385            mEditor.restoreInstanceState(ss.editorState);
5386        }
5387    }
5388
5389    /**
5390     * Control whether this text view saves its entire text contents when
5391     * freezing to an icicle, in addition to dynamic state such as cursor
5392     * position.  By default this is false, not saving the text.  Set to true
5393     * if the text in the text view is not being saved somewhere else in
5394     * persistent storage (such as in a content provider) so that if the
5395     * view is later thawed the user will not lose their data. For
5396     * {@link android.widget.EditText} it is always enabled, regardless of
5397     * the value of the attribute.
5398     *
5399     * @param freezesText Controls whether a frozen icicle should include the
5400     * entire text data: true to include it, false to not.
5401     *
5402     * @attr ref android.R.styleable#TextView_freezesText
5403     */
5404    @android.view.RemotableViewMethod
5405    public void setFreezesText(boolean freezesText) {
5406        mFreezesText = freezesText;
5407    }
5408
5409    /**
5410     * Return whether this text view is including its entire text contents
5411     * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5412     *
5413     * @return Returns true if text is included, false if it isn't.
5414     *
5415     * @see #setFreezesText
5416     */
5417    public boolean getFreezesText() {
5418        return mFreezesText;
5419    }
5420
5421    ///////////////////////////////////////////////////////////////////////////
5422
5423    /**
5424     * Sets the Factory used to create new {@link Editable Editables}.
5425     *
5426     * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
5427     *
5428     * @see android.text.Editable.Factory
5429     * @see android.widget.TextView.BufferType#EDITABLE
5430     */
5431    public final void setEditableFactory(Editable.Factory factory) {
5432        mEditableFactory = factory;
5433        setText(mText);
5434    }
5435
5436    /**
5437     * Sets the Factory used to create new {@link Spannable Spannables}.
5438     *
5439     * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
5440     *
5441     * @see android.text.Spannable.Factory
5442     * @see android.widget.TextView.BufferType#SPANNABLE
5443     */
5444    public final void setSpannableFactory(Spannable.Factory factory) {
5445        mSpannableFactory = factory;
5446        setText(mText);
5447    }
5448
5449    /**
5450     * Sets the text to be displayed. TextView <em>does not</em> accept
5451     * HTML-like formatting, which you can do with text strings in XML resource files.
5452     * To style your strings, attach android.text.style.* objects to a
5453     * {@link android.text.SpannableString}, or see the
5454     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
5455     * Available Resource Types</a> documentation for an example of setting
5456     * formatted text in the XML resource file.
5457     * <p/>
5458     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5459     * intermediate {@link Spannable Spannables}. Likewise it will use
5460     * {@link android.text.Editable.Factory} to create final or intermediate
5461     * {@link Editable Editables}.
5462     *
5463     * @param text text to be displayed
5464     *
5465     * @attr ref android.R.styleable#TextView_text
5466     */
5467    @android.view.RemotableViewMethod
5468    public final void setText(CharSequence text) {
5469        setText(text, mBufferType);
5470    }
5471
5472    /**
5473     * Sets the text to be displayed but retains the cursor position. Same as
5474     * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
5475     * new text.
5476     * <p/>
5477     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5478     * intermediate {@link Spannable Spannables}. Likewise it will use
5479     * {@link android.text.Editable.Factory} to create final or intermediate
5480     * {@link Editable Editables}.
5481     *
5482     * @param text text to be displayed
5483     *
5484     * @see #setText(CharSequence)
5485     */
5486    @android.view.RemotableViewMethod
5487    public final void setTextKeepState(CharSequence text) {
5488        setTextKeepState(text, mBufferType);
5489    }
5490
5491    /**
5492     * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
5493     * <p/>
5494     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5495     * intermediate {@link Spannable Spannables}. Likewise it will use
5496     * {@link android.text.Editable.Factory} to create final or intermediate
5497     * {@link Editable Editables}.
5498     *
5499     * @param text text to be displayed
5500     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5501     *              stored as a static text, styleable/spannable text, or editable text
5502     *
5503     * @see #setText(CharSequence)
5504     * @see android.widget.TextView.BufferType
5505     * @see #setSpannableFactory(Spannable.Factory)
5506     * @see #setEditableFactory(Editable.Factory)
5507     *
5508     * @attr ref android.R.styleable#TextView_text
5509     * @attr ref android.R.styleable#TextView_bufferType
5510     */
5511    public void setText(CharSequence text, BufferType type) {
5512        setText(text, type, true, 0);
5513
5514        if (mCharWrapper != null) {
5515            mCharWrapper.mChars = null;
5516        }
5517    }
5518
5519    private void setText(CharSequence text, BufferType type,
5520                         boolean notifyBefore, int oldlen) {
5521        mTextSetFromXmlOrResourceId = false;
5522        if (text == null) {
5523            text = "";
5524        }
5525
5526        // If suggestions are not enabled, remove the suggestion spans from the text
5527        if (!isSuggestionsEnabled()) {
5528            text = removeSuggestionSpans(text);
5529        }
5530
5531        if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
5532
5533        if (text instanceof Spanned
5534                && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
5535            if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
5536                setHorizontalFadingEdgeEnabled(true);
5537                mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
5538            } else {
5539                setHorizontalFadingEdgeEnabled(false);
5540                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
5541            }
5542            setEllipsize(TextUtils.TruncateAt.MARQUEE);
5543        }
5544
5545        int n = mFilters.length;
5546        for (int i = 0; i < n; i++) {
5547            CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
5548            if (out != null) {
5549                text = out;
5550            }
5551        }
5552
5553        if (notifyBefore) {
5554            if (mText != null) {
5555                oldlen = mText.length();
5556                sendBeforeTextChanged(mText, 0, oldlen, text.length());
5557            } else {
5558                sendBeforeTextChanged("", 0, 0, text.length());
5559            }
5560        }
5561
5562        boolean needEditableForNotification = false;
5563
5564        if (mListeners != null && mListeners.size() != 0) {
5565            needEditableForNotification = true;
5566        }
5567
5568        if (type == BufferType.EDITABLE || getKeyListener() != null
5569                || needEditableForNotification) {
5570            createEditorIfNeeded();
5571            mEditor.forgetUndoRedo();
5572            Editable t = mEditableFactory.newEditable(text);
5573            text = t;
5574            setFilters(t, mFilters);
5575            InputMethodManager imm = InputMethodManager.peekInstance();
5576            if (imm != null) imm.restartInput(this);
5577        } else if (type == BufferType.SPANNABLE || mMovement != null) {
5578            text = mSpannableFactory.newSpannable(text);
5579        } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
5580            text = TextUtils.stringOrSpannedString(text);
5581        }
5582
5583        if (mAutoLinkMask != 0) {
5584            Spannable s2;
5585
5586            if (type == BufferType.EDITABLE || text instanceof Spannable) {
5587                s2 = (Spannable) text;
5588            } else {
5589                s2 = mSpannableFactory.newSpannable(text);
5590            }
5591
5592            if (Linkify.addLinks(s2, mAutoLinkMask)) {
5593                text = s2;
5594                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
5595
5596                /*
5597                 * We must go ahead and set the text before changing the
5598                 * movement method, because setMovementMethod() may call
5599                 * setText() again to try to upgrade the buffer type.
5600                 */
5601                mText = text;
5602
5603                // Do not change the movement method for text that support text selection as it
5604                // would prevent an arbitrary cursor displacement.
5605                if (mLinksClickable && !textCanBeSelected()) {
5606                    setMovementMethod(LinkMovementMethod.getInstance());
5607                }
5608            }
5609        }
5610
5611        mBufferType = type;
5612        mText = text;
5613
5614        if (mTransformation == null) {
5615            mTransformed = text;
5616        } else {
5617            mTransformed = mTransformation.getTransformation(text, this);
5618        }
5619
5620        final int textLength = text.length();
5621
5622        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
5623            Spannable sp = (Spannable) text;
5624
5625            // Remove any ChangeWatchers that might have come from other TextViews.
5626            final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
5627            final int count = watchers.length;
5628            for (int i = 0; i < count; i++) {
5629                sp.removeSpan(watchers[i]);
5630            }
5631
5632            if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
5633
5634            sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
5635                    | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
5636
5637            if (mEditor != null) mEditor.addSpanWatchers(sp);
5638
5639            if (mTransformation != null) {
5640                sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
5641            }
5642
5643            if (mMovement != null) {
5644                mMovement.initialize(this, (Spannable) text);
5645
5646                /*
5647                 * Initializing the movement method will have set the
5648                 * selection, so reset mSelectionMoved to keep that from
5649                 * interfering with the normal on-focus selection-setting.
5650                 */
5651                if (mEditor != null) mEditor.mSelectionMoved = false;
5652            }
5653        }
5654
5655        if (mLayout != null) {
5656            checkForRelayout();
5657        }
5658
5659        sendOnTextChanged(text, 0, oldlen, textLength);
5660        onTextChanged(text, 0, oldlen, textLength);
5661
5662        notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
5663
5664        if (needEditableForNotification) {
5665            sendAfterTextChanged((Editable) text);
5666        } else {
5667            // Always notify AutoFillManager - it will return right away if autofill is disabled.
5668            notifyAutoFillManagerAfterTextChangedIfNeeded();
5669        }
5670
5671        // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
5672        if (mEditor != null) mEditor.prepareCursorControllers();
5673    }
5674
5675    /**
5676     * Sets the TextView to display the specified slice of the specified
5677     * char array. You must promise that you will not change the contents
5678     * of the array except for right before another call to setText(),
5679     * since the TextView has no way to know that the text
5680     * has changed and that it needs to invalidate and re-layout.
5681     *
5682     * @param text char array to be displayed
5683     * @param start start index in the char array
5684     * @param len length of char count after {@code start}
5685     */
5686    public final void setText(char[] text, int start, int len) {
5687        int oldlen = 0;
5688
5689        if (start < 0 || len < 0 || start + len > text.length) {
5690            throw new IndexOutOfBoundsException(start + ", " + len);
5691        }
5692
5693        /*
5694         * We must do the before-notification here ourselves because if
5695         * the old text is a CharWrapper we destroy it before calling
5696         * into the normal path.
5697         */
5698        if (mText != null) {
5699            oldlen = mText.length();
5700            sendBeforeTextChanged(mText, 0, oldlen, len);
5701        } else {
5702            sendBeforeTextChanged("", 0, 0, len);
5703        }
5704
5705        if (mCharWrapper == null) {
5706            mCharWrapper = new CharWrapper(text, start, len);
5707        } else {
5708            mCharWrapper.set(text, start, len);
5709        }
5710
5711        setText(mCharWrapper, mBufferType, false, oldlen);
5712    }
5713
5714    /**
5715     * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
5716     * the cursor position. Same as
5717     * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
5718     * position (if any) is retained in the new text.
5719     * <p/>
5720     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5721     * intermediate {@link Spannable Spannables}. Likewise it will use
5722     * {@link android.text.Editable.Factory} to create final or intermediate
5723     * {@link Editable Editables}.
5724     *
5725     * @param text text to be displayed
5726     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5727     *              stored as a static text, styleable/spannable text, or editable text
5728     *
5729     * @see #setText(CharSequence, android.widget.TextView.BufferType)
5730     */
5731    public final void setTextKeepState(CharSequence text, BufferType type) {
5732        int start = getSelectionStart();
5733        int end = getSelectionEnd();
5734        int len = text.length();
5735
5736        setText(text, type);
5737
5738        if (start >= 0 || end >= 0) {
5739            if (mText instanceof Spannable) {
5740                Selection.setSelection((Spannable) mText,
5741                                       Math.max(0, Math.min(start, len)),
5742                                       Math.max(0, Math.min(end, len)));
5743            }
5744        }
5745    }
5746
5747    /**
5748     * Sets the text to be displayed using a string resource identifier.
5749     *
5750     * @param resid the resource identifier of the string resource to be displayed
5751     *
5752     * @see #setText(CharSequence)
5753     *
5754     * @attr ref android.R.styleable#TextView_text
5755     */
5756    @android.view.RemotableViewMethod
5757    public final void setText(@StringRes int resid) {
5758        setText(getContext().getResources().getText(resid));
5759        mTextSetFromXmlOrResourceId = true;
5760        mTextId = resid;
5761    }
5762
5763    /**
5764     * Sets the text to be displayed using a string resource identifier and the
5765     * {@link android.widget.TextView.BufferType}.
5766     * <p/>
5767     * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5768     * intermediate {@link Spannable Spannables}. Likewise it will use
5769     * {@link android.text.Editable.Factory} to create final or intermediate
5770     * {@link Editable Editables}.
5771     *
5772     * @param resid the resource identifier of the string resource to be displayed
5773     * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5774     *              stored as a static text, styleable/spannable text, or editable text
5775     *
5776     * @see #setText(int)
5777     * @see #setText(CharSequence)
5778     * @see android.widget.TextView.BufferType
5779     * @see #setSpannableFactory(Spannable.Factory)
5780     * @see #setEditableFactory(Editable.Factory)
5781     *
5782     * @attr ref android.R.styleable#TextView_text
5783     * @attr ref android.R.styleable#TextView_bufferType
5784     */
5785    public final void setText(@StringRes int resid, BufferType type) {
5786        setText(getContext().getResources().getText(resid), type);
5787        mTextSetFromXmlOrResourceId = true;
5788        mTextId = resid;
5789    }
5790
5791    /**
5792     * Sets the text to be displayed when the text of the TextView is empty.
5793     * Null means to use the normal empty text. The hint does not currently
5794     * participate in determining the size of the view.
5795     *
5796     * @attr ref android.R.styleable#TextView_hint
5797     */
5798    @android.view.RemotableViewMethod
5799    public final void setHint(CharSequence hint) {
5800        setHintInternal(hint);
5801
5802        if (mEditor != null && isInputMethodTarget()) {
5803            mEditor.reportExtractedText();
5804        }
5805    }
5806
5807    private void setHintInternal(CharSequence hint) {
5808        mHint = TextUtils.stringOrSpannedString(hint);
5809
5810        if (mLayout != null) {
5811            checkForRelayout();
5812        }
5813
5814        if (mText.length() == 0) {
5815            invalidate();
5816        }
5817
5818        // Invalidate display list if hint is currently used
5819        if (mEditor != null && mText.length() == 0 && mHint != null) {
5820            mEditor.invalidateTextDisplayList();
5821        }
5822    }
5823
5824    /**
5825     * Sets the text to be displayed when the text of the TextView is empty,
5826     * from a resource.
5827     *
5828     * @attr ref android.R.styleable#TextView_hint
5829     */
5830    @android.view.RemotableViewMethod
5831    public final void setHint(@StringRes int resid) {
5832        setHint(getContext().getResources().getText(resid));
5833    }
5834
5835    /**
5836     * Returns the hint that is displayed when the text of the TextView
5837     * is empty.
5838     *
5839     * @attr ref android.R.styleable#TextView_hint
5840     */
5841    @ViewDebug.CapturedViewProperty
5842    public CharSequence getHint() {
5843        return mHint;
5844    }
5845
5846    boolean isSingleLine() {
5847        return mSingleLine;
5848    }
5849
5850    private static boolean isMultilineInputType(int type) {
5851        return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
5852                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
5853    }
5854
5855    /**
5856     * Removes the suggestion spans.
5857     */
5858    CharSequence removeSuggestionSpans(CharSequence text) {
5859        if (text instanceof Spanned) {
5860            Spannable spannable;
5861            if (text instanceof Spannable) {
5862                spannable = (Spannable) text;
5863            } else {
5864                spannable = mSpannableFactory.newSpannable(text);
5865            }
5866
5867            SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
5868            if (spans.length == 0) {
5869                return text;
5870            } else {
5871                text = spannable;
5872            }
5873
5874            for (int i = 0; i < spans.length; i++) {
5875                spannable.removeSpan(spans[i]);
5876            }
5877        }
5878        return text;
5879    }
5880
5881    /**
5882     * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
5883     * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
5884     * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
5885     * then a soft keyboard will not be displayed for this text view.
5886     *
5887     * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
5888     * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
5889     * type.
5890     *
5891     * @see #getInputType()
5892     * @see #setRawInputType(int)
5893     * @see android.text.InputType
5894     * @attr ref android.R.styleable#TextView_inputType
5895     */
5896    public void setInputType(int type) {
5897        final boolean wasPassword = isPasswordInputType(getInputType());
5898        final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
5899        setInputType(type, false);
5900        final boolean isPassword = isPasswordInputType(type);
5901        final boolean isVisiblePassword = isVisiblePasswordInputType(type);
5902        boolean forceUpdate = false;
5903        if (isPassword) {
5904            setTransformationMethod(PasswordTransformationMethod.getInstance());
5905            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5906        } else if (isVisiblePassword) {
5907            if (mTransformation == PasswordTransformationMethod.getInstance()) {
5908                forceUpdate = true;
5909            }
5910            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5911        } else if (wasPassword || wasVisiblePassword) {
5912            // not in password mode, clean up typeface and transformation
5913            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
5914            if (mTransformation == PasswordTransformationMethod.getInstance()) {
5915                forceUpdate = true;
5916            }
5917        }
5918
5919        boolean singleLine = !isMultilineInputType(type);
5920
5921        // We need to update the single line mode if it has changed or we
5922        // were previously in password mode.
5923        if (mSingleLine != singleLine || forceUpdate) {
5924            // Change single line mode, but only change the transformation if
5925            // we are not in password mode.
5926            applySingleLine(singleLine, !isPassword, true);
5927        }
5928
5929        if (!isSuggestionsEnabled()) {
5930            mText = removeSuggestionSpans(mText);
5931        }
5932
5933        InputMethodManager imm = InputMethodManager.peekInstance();
5934        if (imm != null) imm.restartInput(this);
5935    }
5936
5937    /**
5938     * It would be better to rely on the input type for everything. A password inputType should have
5939     * a password transformation. We should hence use isPasswordInputType instead of this method.
5940     *
5941     * We should:
5942     * - Call setInputType in setKeyListener instead of changing the input type directly (which
5943     * would install the correct transformation).
5944     * - Refuse the installation of a non-password transformation in setTransformation if the input
5945     * type is password.
5946     *
5947     * However, this is like this for legacy reasons and we cannot break existing apps. This method
5948     * is useful since it matches what the user can see (obfuscated text or not).
5949     *
5950     * @return true if the current transformation method is of the password type.
5951     */
5952    boolean hasPasswordTransformationMethod() {
5953        return mTransformation instanceof PasswordTransformationMethod;
5954    }
5955
5956    static boolean isPasswordInputType(int inputType) {
5957        final int variation =
5958                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5959        return variation
5960                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
5961                || variation
5962                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
5963                || variation
5964                == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
5965    }
5966
5967    private static boolean isVisiblePasswordInputType(int inputType) {
5968        final int variation =
5969                inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5970        return variation
5971                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
5972    }
5973
5974    /**
5975     * Directly change the content type integer of the text view, without
5976     * modifying any other state.
5977     * @see #setInputType(int)
5978     * @see android.text.InputType
5979     * @attr ref android.R.styleable#TextView_inputType
5980     */
5981    public void setRawInputType(int type) {
5982        if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
5983        createEditorIfNeeded();
5984        mEditor.mInputType = type;
5985    }
5986
5987    /**
5988     * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
5989     *         a {@code Locale} object that can be used to customize key various listeners.
5990     * @see DateKeyListener#getInstance(Locale)
5991     * @see DateTimeKeyListener#getInstance(Locale)
5992     * @see DigitsKeyListener#getInstance(Locale)
5993     * @see TimeKeyListener#getInstance(Locale)
5994     */
5995    @Nullable
5996    private Locale getCustomLocaleForKeyListenerOrNull() {
5997        if (!mUseInternationalizedInput) {
5998            // If the application does not target O, stick to the previous behavior.
5999            return null;
6000        }
6001        final LocaleList locales = getImeHintLocales();
6002        if (locales == null) {
6003            // If the application does not explicitly specify IME hint locale, also stick to the
6004            // previous behavior.
6005            return null;
6006        }
6007        return locales.get(0);
6008    }
6009
6010    private void setInputType(int type, boolean direct) {
6011        final int cls = type & EditorInfo.TYPE_MASK_CLASS;
6012        KeyListener input;
6013        if (cls == EditorInfo.TYPE_CLASS_TEXT) {
6014            boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
6015            TextKeyListener.Capitalize cap;
6016            if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
6017                cap = TextKeyListener.Capitalize.CHARACTERS;
6018            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
6019                cap = TextKeyListener.Capitalize.WORDS;
6020            } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
6021                cap = TextKeyListener.Capitalize.SENTENCES;
6022            } else {
6023                cap = TextKeyListener.Capitalize.NONE;
6024            }
6025            input = TextKeyListener.getInstance(autotext, cap);
6026        } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
6027            final Locale locale = getCustomLocaleForKeyListenerOrNull();
6028            input = DigitsKeyListener.getInstance(
6029                    locale,
6030                    (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
6031                    (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
6032            if (locale != null) {
6033                // Override type, if necessary for i18n.
6034                int newType = input.getInputType();
6035                final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
6036                if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
6037                    // The class is different from the original class. So we need to override
6038                    // 'type'. But we want to keep the password flag if it's there.
6039                    if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
6040                        newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
6041                    }
6042                    type = newType;
6043                }
6044            }
6045        } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
6046            final Locale locale = getCustomLocaleForKeyListenerOrNull();
6047            switch (type & EditorInfo.TYPE_MASK_VARIATION) {
6048                case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
6049                    input = DateKeyListener.getInstance(locale);
6050                    break;
6051                case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
6052                    input = TimeKeyListener.getInstance(locale);
6053                    break;
6054                default:
6055                    input = DateTimeKeyListener.getInstance(locale);
6056                    break;
6057            }
6058            if (mUseInternationalizedInput) {
6059                type = input.getInputType(); // Override type, if necessary for i18n.
6060            }
6061        } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
6062            input = DialerKeyListener.getInstance();
6063        } else {
6064            input = TextKeyListener.getInstance();
6065        }
6066        setRawInputType(type);
6067        mListenerChanged = false;
6068        if (direct) {
6069            createEditorIfNeeded();
6070            mEditor.mKeyListener = input;
6071        } else {
6072            setKeyListenerOnly(input);
6073        }
6074    }
6075
6076    /**
6077     * Get the type of the editable content.
6078     *
6079     * @see #setInputType(int)
6080     * @see android.text.InputType
6081     */
6082    public int getInputType() {
6083        return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
6084    }
6085
6086    /**
6087     * Change the editor type integer associated with the text view, which
6088     * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
6089     * when it has focus.
6090     * @see #getImeOptions
6091     * @see android.view.inputmethod.EditorInfo
6092     * @attr ref android.R.styleable#TextView_imeOptions
6093     */
6094    public void setImeOptions(int imeOptions) {
6095        createEditorIfNeeded();
6096        mEditor.createInputContentTypeIfNeeded();
6097        mEditor.mInputContentType.imeOptions = imeOptions;
6098    }
6099
6100    /**
6101     * Get the type of the Input Method Editor (IME).
6102     * @return the type of the IME
6103     * @see #setImeOptions(int)
6104     * @see android.view.inputmethod.EditorInfo
6105     */
6106    public int getImeOptions() {
6107        return mEditor != null && mEditor.mInputContentType != null
6108                ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
6109    }
6110
6111    /**
6112     * Change the custom IME action associated with the text view, which
6113     * will be reported to an IME with {@link EditorInfo#actionLabel}
6114     * and {@link EditorInfo#actionId} when it has focus.
6115     * @see #getImeActionLabel
6116     * @see #getImeActionId
6117     * @see android.view.inputmethod.EditorInfo
6118     * @attr ref android.R.styleable#TextView_imeActionLabel
6119     * @attr ref android.R.styleable#TextView_imeActionId
6120     */
6121    public void setImeActionLabel(CharSequence label, int actionId) {
6122        createEditorIfNeeded();
6123        mEditor.createInputContentTypeIfNeeded();
6124        mEditor.mInputContentType.imeActionLabel = label;
6125        mEditor.mInputContentType.imeActionId = actionId;
6126    }
6127
6128    /**
6129     * Get the IME action label previous set with {@link #setImeActionLabel}.
6130     *
6131     * @see #setImeActionLabel
6132     * @see android.view.inputmethod.EditorInfo
6133     */
6134    public CharSequence getImeActionLabel() {
6135        return mEditor != null && mEditor.mInputContentType != null
6136                ? mEditor.mInputContentType.imeActionLabel : null;
6137    }
6138
6139    /**
6140     * Get the IME action ID previous set with {@link #setImeActionLabel}.
6141     *
6142     * @see #setImeActionLabel
6143     * @see android.view.inputmethod.EditorInfo
6144     */
6145    public int getImeActionId() {
6146        return mEditor != null && mEditor.mInputContentType != null
6147                ? mEditor.mInputContentType.imeActionId : 0;
6148    }
6149
6150    /**
6151     * Set a special listener to be called when an action is performed
6152     * on the text view.  This will be called when the enter key is pressed,
6153     * or when an action supplied to the IME is selected by the user.  Setting
6154     * this means that the normal hard key event will not insert a newline
6155     * into the text view, even if it is multi-line; holding down the ALT
6156     * modifier will, however, allow the user to insert a newline character.
6157     */
6158    public void setOnEditorActionListener(OnEditorActionListener l) {
6159        createEditorIfNeeded();
6160        mEditor.createInputContentTypeIfNeeded();
6161        mEditor.mInputContentType.onEditorActionListener = l;
6162    }
6163
6164    /**
6165     * Called when an attached input method calls
6166     * {@link InputConnection#performEditorAction(int)
6167     * InputConnection.performEditorAction()}
6168     * for this text view.  The default implementation will call your action
6169     * listener supplied to {@link #setOnEditorActionListener}, or perform
6170     * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
6171     * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
6172     * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
6173     * EditorInfo.IME_ACTION_DONE}.
6174     *
6175     * <p>For backwards compatibility, if no IME options have been set and the
6176     * text view would not normally advance focus on enter, then
6177     * the NEXT and DONE actions received here will be turned into an enter
6178     * key down/up pair to go through the normal key handling.
6179     *
6180     * @param actionCode The code of the action being performed.
6181     *
6182     * @see #setOnEditorActionListener
6183     */
6184    public void onEditorAction(int actionCode) {
6185        final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
6186        if (ict != null) {
6187            if (ict.onEditorActionListener != null) {
6188                if (ict.onEditorActionListener.onEditorAction(this,
6189                        actionCode, null)) {
6190                    return;
6191                }
6192            }
6193
6194            // This is the handling for some default action.
6195            // Note that for backwards compatibility we don't do this
6196            // default handling if explicit ime options have not been given,
6197            // instead turning this into the normal enter key codes that an
6198            // app may be expecting.
6199            if (actionCode == EditorInfo.IME_ACTION_NEXT) {
6200                View v = focusSearch(FOCUS_FORWARD);
6201                if (v != null) {
6202                    if (!v.requestFocus(FOCUS_FORWARD)) {
6203                        throw new IllegalStateException("focus search returned a view "
6204                                + "that wasn't able to take focus!");
6205                    }
6206                }
6207                return;
6208
6209            } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
6210                View v = focusSearch(FOCUS_BACKWARD);
6211                if (v != null) {
6212                    if (!v.requestFocus(FOCUS_BACKWARD)) {
6213                        throw new IllegalStateException("focus search returned a view "
6214                                + "that wasn't able to take focus!");
6215                    }
6216                }
6217                return;
6218
6219            } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
6220                InputMethodManager imm = InputMethodManager.peekInstance();
6221                if (imm != null && imm.isActive(this)) {
6222                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
6223                }
6224                return;
6225            }
6226        }
6227
6228        ViewRootImpl viewRootImpl = getViewRootImpl();
6229        if (viewRootImpl != null) {
6230            long eventTime = SystemClock.uptimeMillis();
6231            viewRootImpl.dispatchKeyFromIme(
6232                    new KeyEvent(eventTime, eventTime,
6233                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
6234                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
6235                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
6236                    | KeyEvent.FLAG_EDITOR_ACTION));
6237            viewRootImpl.dispatchKeyFromIme(
6238                    new KeyEvent(SystemClock.uptimeMillis(), eventTime,
6239                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
6240                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
6241                    KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
6242                    | KeyEvent.FLAG_EDITOR_ACTION));
6243        }
6244    }
6245
6246    /**
6247     * Set the private content type of the text, which is the
6248     * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
6249     * field that will be filled in when creating an input connection.
6250     *
6251     * @see #getPrivateImeOptions()
6252     * @see EditorInfo#privateImeOptions
6253     * @attr ref android.R.styleable#TextView_privateImeOptions
6254     */
6255    public void setPrivateImeOptions(String type) {
6256        createEditorIfNeeded();
6257        mEditor.createInputContentTypeIfNeeded();
6258        mEditor.mInputContentType.privateImeOptions = type;
6259    }
6260
6261    /**
6262     * Get the private type of the content.
6263     *
6264     * @see #setPrivateImeOptions(String)
6265     * @see EditorInfo#privateImeOptions
6266     */
6267    public String getPrivateImeOptions() {
6268        return mEditor != null && mEditor.mInputContentType != null
6269                ? mEditor.mInputContentType.privateImeOptions : null;
6270    }
6271
6272    /**
6273     * Set the extra input data of the text, which is the
6274     * {@link EditorInfo#extras TextBoxAttribute.extras}
6275     * Bundle that will be filled in when creating an input connection.  The
6276     * given integer is the resource identifier of an XML resource holding an
6277     * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
6278     *
6279     * @see #getInputExtras(boolean)
6280     * @see EditorInfo#extras
6281     * @attr ref android.R.styleable#TextView_editorExtras
6282     */
6283    public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
6284        createEditorIfNeeded();
6285        XmlResourceParser parser = getResources().getXml(xmlResId);
6286        mEditor.createInputContentTypeIfNeeded();
6287        mEditor.mInputContentType.extras = new Bundle();
6288        getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
6289    }
6290
6291    /**
6292     * Retrieve the input extras currently associated with the text view, which
6293     * can be viewed as well as modified.
6294     *
6295     * @param create If true, the extras will be created if they don't already
6296     * exist.  Otherwise, null will be returned if none have been created.
6297     * @see #setInputExtras(int)
6298     * @see EditorInfo#extras
6299     * @attr ref android.R.styleable#TextView_editorExtras
6300     */
6301    public Bundle getInputExtras(boolean create) {
6302        if (mEditor == null && !create) return null;
6303        createEditorIfNeeded();
6304        if (mEditor.mInputContentType == null) {
6305            if (!create) return null;
6306            mEditor.createInputContentTypeIfNeeded();
6307        }
6308        if (mEditor.mInputContentType.extras == null) {
6309            if (!create) return null;
6310            mEditor.mInputContentType.extras = new Bundle();
6311        }
6312        return mEditor.mInputContentType.extras;
6313    }
6314
6315    /**
6316     * Change "hint" locales associated with the text view, which will be reported to an IME with
6317     * {@link EditorInfo#hintLocales} when it has focus.
6318     *
6319     * Starting with Android O, this also causes internationalized listeners to be created (or
6320     * change locale) based on the first locale in the input locale list.
6321     *
6322     * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
6323     * call {@link InputMethodManager#restartInput(View)}.</p>
6324     * @param hintLocales List of the languages that the user is supposed to switch to no matter
6325     * what input method subtype is currently used. Set {@code null} to clear the current "hint".
6326     * @see #getImeHintLocales()
6327     * @see android.view.inputmethod.EditorInfo#hintLocales
6328     */
6329    public void setImeHintLocales(@Nullable LocaleList hintLocales) {
6330        createEditorIfNeeded();
6331        mEditor.createInputContentTypeIfNeeded();
6332        mEditor.mInputContentType.imeHintLocales = hintLocales;
6333        if (mUseInternationalizedInput) {
6334            changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
6335        }
6336    }
6337
6338    /**
6339     * @return The current languages list "hint". {@code null} when no "hint" is available.
6340     * @see #setImeHintLocales(LocaleList)
6341     * @see android.view.inputmethod.EditorInfo#hintLocales
6342     */
6343    @Nullable
6344    public LocaleList getImeHintLocales() {
6345        if (mEditor == null) {
6346            return null;
6347        }
6348        if (mEditor.mInputContentType == null) {
6349            return null;
6350        }
6351        return mEditor.mInputContentType.imeHintLocales;
6352    }
6353
6354    /**
6355     * Returns the error message that was set to be displayed with
6356     * {@link #setError}, or <code>null</code> if no error was set
6357     * or if it the error was cleared by the widget after user input.
6358     */
6359    public CharSequence getError() {
6360        return mEditor == null ? null : mEditor.mError;
6361    }
6362
6363    /**
6364     * Sets the right-hand compound drawable of the TextView to the "error"
6365     * icon and sets an error message that will be displayed in a popup when
6366     * the TextView has focus.  The icon and error message will be reset to
6367     * null when any key events cause changes to the TextView's text.  If the
6368     * <code>error</code> is <code>null</code>, the error message and icon
6369     * will be cleared.
6370     */
6371    @android.view.RemotableViewMethod
6372    public void setError(CharSequence error) {
6373        if (error == null) {
6374            setError(null, null);
6375        } else {
6376            Drawable dr = getContext().getDrawable(
6377                    com.android.internal.R.drawable.indicator_input_error);
6378
6379            dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
6380            setError(error, dr);
6381        }
6382    }
6383
6384    /**
6385     * Sets the right-hand compound drawable of the TextView to the specified
6386     * icon and sets an error message that will be displayed in a popup when
6387     * the TextView has focus.  The icon and error message will be reset to
6388     * null when any key events cause changes to the TextView's text.  The
6389     * drawable must already have had {@link Drawable#setBounds} set on it.
6390     * If the <code>error</code> is <code>null</code>, the error message will
6391     * be cleared (and you should provide a <code>null</code> icon as well).
6392     */
6393    public void setError(CharSequence error, Drawable icon) {
6394        createEditorIfNeeded();
6395        mEditor.setError(error, icon);
6396        notifyAccessibilityStateChanged(
6397                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
6398    }
6399
6400    @Override
6401    protected boolean setFrame(int l, int t, int r, int b) {
6402        boolean result = super.setFrame(l, t, r, b);
6403
6404        if (mEditor != null) mEditor.setFrame();
6405
6406        restartMarqueeIfNeeded();
6407
6408        return result;
6409    }
6410
6411    private void restartMarqueeIfNeeded() {
6412        if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6413            mRestartMarquee = false;
6414            startMarquee();
6415        }
6416    }
6417
6418    /**
6419     * Sets the list of input filters that will be used if the buffer is
6420     * Editable. Has no effect otherwise.
6421     *
6422     * @attr ref android.R.styleable#TextView_maxLength
6423     */
6424    public void setFilters(InputFilter[] filters) {
6425        if (filters == null) {
6426            throw new IllegalArgumentException();
6427        }
6428
6429        mFilters = filters;
6430
6431        if (mText instanceof Editable) {
6432            setFilters((Editable) mText, filters);
6433        }
6434    }
6435
6436    /**
6437     * Sets the list of input filters on the specified Editable,
6438     * and includes mInput in the list if it is an InputFilter.
6439     */
6440    private void setFilters(Editable e, InputFilter[] filters) {
6441        if (mEditor != null) {
6442            final boolean undoFilter = mEditor.mUndoInputFilter != null;
6443            final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
6444            int num = 0;
6445            if (undoFilter) num++;
6446            if (keyFilter) num++;
6447            if (num > 0) {
6448                InputFilter[] nf = new InputFilter[filters.length + num];
6449
6450                System.arraycopy(filters, 0, nf, 0, filters.length);
6451                num = 0;
6452                if (undoFilter) {
6453                    nf[filters.length] = mEditor.mUndoInputFilter;
6454                    num++;
6455                }
6456                if (keyFilter) {
6457                    nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
6458                }
6459
6460                e.setFilters(nf);
6461                return;
6462            }
6463        }
6464        e.setFilters(filters);
6465    }
6466
6467    /**
6468     * Returns the current list of input filters.
6469     *
6470     * @attr ref android.R.styleable#TextView_maxLength
6471     */
6472    public InputFilter[] getFilters() {
6473        return mFilters;
6474    }
6475
6476    /////////////////////////////////////////////////////////////////////////
6477
6478    private int getBoxHeight(Layout l) {
6479        Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
6480        int padding = (l == mHintLayout)
6481                ? getCompoundPaddingTop() + getCompoundPaddingBottom()
6482                : getExtendedPaddingTop() + getExtendedPaddingBottom();
6483        return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
6484    }
6485
6486    int getVerticalOffset(boolean forceNormal) {
6487        int voffset = 0;
6488        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6489
6490        Layout l = mLayout;
6491        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6492            l = mHintLayout;
6493        }
6494
6495        if (gravity != Gravity.TOP) {
6496            int boxht = getBoxHeight(l);
6497            int textht = l.getHeight();
6498
6499            if (textht < boxht) {
6500                if (gravity == Gravity.BOTTOM) {
6501                    voffset = boxht - textht;
6502                } else { // (gravity == Gravity.CENTER_VERTICAL)
6503                    voffset = (boxht - textht) >> 1;
6504                }
6505            }
6506        }
6507        return voffset;
6508    }
6509
6510    private int getBottomVerticalOffset(boolean forceNormal) {
6511        int voffset = 0;
6512        final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6513
6514        Layout l = mLayout;
6515        if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6516            l = mHintLayout;
6517        }
6518
6519        if (gravity != Gravity.BOTTOM) {
6520            int boxht = getBoxHeight(l);
6521            int textht = l.getHeight();
6522
6523            if (textht < boxht) {
6524                if (gravity == Gravity.TOP) {
6525                    voffset = boxht - textht;
6526                } else { // (gravity == Gravity.CENTER_VERTICAL)
6527                    voffset = (boxht - textht) >> 1;
6528                }
6529            }
6530        }
6531        return voffset;
6532    }
6533
6534    void invalidateCursorPath() {
6535        if (mHighlightPathBogus) {
6536            invalidateCursor();
6537        } else {
6538            final int horizontalPadding = getCompoundPaddingLeft();
6539            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6540
6541            if (mEditor.mDrawableForCursor == null) {
6542                synchronized (TEMP_RECTF) {
6543                    /*
6544                     * The reason for this concern about the thickness of the
6545                     * cursor and doing the floor/ceil on the coordinates is that
6546                     * some EditTexts (notably textfields in the Browser) have
6547                     * anti-aliased text where not all the characters are
6548                     * necessarily at integer-multiple locations.  This should
6549                     * make sure the entire cursor gets invalidated instead of
6550                     * sometimes missing half a pixel.
6551                     */
6552                    float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
6553                    if (thick < 1.0f) {
6554                        thick = 1.0f;
6555                    }
6556
6557                    thick /= 2.0f;
6558
6559                    // mHighlightPath is guaranteed to be non null at that point.
6560                    mHighlightPath.computeBounds(TEMP_RECTF, false);
6561
6562                    invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
6563                            (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
6564                            (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
6565                            (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
6566                }
6567            } else {
6568                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
6569                invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
6570                        bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
6571            }
6572        }
6573    }
6574
6575    void invalidateCursor() {
6576        int where = getSelectionEnd();
6577
6578        invalidateCursor(where, where, where);
6579    }
6580
6581    private void invalidateCursor(int a, int b, int c) {
6582        if (a >= 0 || b >= 0 || c >= 0) {
6583            int start = Math.min(Math.min(a, b), c);
6584            int end = Math.max(Math.max(a, b), c);
6585            invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
6586        }
6587    }
6588
6589    /**
6590     * Invalidates the region of text enclosed between the start and end text offsets.
6591     */
6592    void invalidateRegion(int start, int end, boolean invalidateCursor) {
6593        if (mLayout == null) {
6594            invalidate();
6595        } else {
6596            int lineStart = mLayout.getLineForOffset(start);
6597            int top = mLayout.getLineTop(lineStart);
6598
6599            // This is ridiculous, but the descent from the line above
6600            // can hang down into the line we really want to redraw,
6601            // so we have to invalidate part of the line above to make
6602            // sure everything that needs to be redrawn really is.
6603            // (But not the whole line above, because that would cause
6604            // the same problem with the descenders on the line above it!)
6605            if (lineStart > 0) {
6606                top -= mLayout.getLineDescent(lineStart - 1);
6607            }
6608
6609            int lineEnd;
6610
6611            if (start == end) {
6612                lineEnd = lineStart;
6613            } else {
6614                lineEnd = mLayout.getLineForOffset(end);
6615            }
6616
6617            int bottom = mLayout.getLineBottom(lineEnd);
6618
6619            // mEditor can be null in case selection is set programmatically.
6620            if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
6621                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
6622                top = Math.min(top, bounds.top);
6623                bottom = Math.max(bottom, bounds.bottom);
6624            }
6625
6626            final int compoundPaddingLeft = getCompoundPaddingLeft();
6627            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6628
6629            int left, right;
6630            if (lineStart == lineEnd && !invalidateCursor) {
6631                left = (int) mLayout.getPrimaryHorizontal(start);
6632                right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
6633                left += compoundPaddingLeft;
6634                right += compoundPaddingLeft;
6635            } else {
6636                // Rectangle bounding box when the region spans several lines
6637                left = compoundPaddingLeft;
6638                right = getWidth() - getCompoundPaddingRight();
6639            }
6640
6641            invalidate(mScrollX + left, verticalPadding + top,
6642                    mScrollX + right, verticalPadding + bottom);
6643        }
6644    }
6645
6646    private void registerForPreDraw() {
6647        if (!mPreDrawRegistered) {
6648            getViewTreeObserver().addOnPreDrawListener(this);
6649            mPreDrawRegistered = true;
6650        }
6651    }
6652
6653    private void unregisterForPreDraw() {
6654        getViewTreeObserver().removeOnPreDrawListener(this);
6655        mPreDrawRegistered = false;
6656        mPreDrawListenerDetached = false;
6657    }
6658
6659    /**
6660     * {@inheritDoc}
6661     */
6662    @Override
6663    public boolean onPreDraw() {
6664        if (mLayout == null) {
6665            assumeLayout();
6666        }
6667
6668        if (mMovement != null) {
6669            /* This code also provides auto-scrolling when a cursor is moved using a
6670             * CursorController (insertion point or selection limits).
6671             * For selection, ensure start or end is visible depending on controller's state.
6672             */
6673            int curs = getSelectionEnd();
6674            // Do not create the controller if it is not already created.
6675            if (mEditor != null && mEditor.mSelectionModifierCursorController != null
6676                    && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
6677                curs = getSelectionStart();
6678            }
6679
6680            /*
6681             * TODO: This should really only keep the end in view if
6682             * it already was before the text changed.  I'm not sure
6683             * of a good way to tell from here if it was.
6684             */
6685            if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6686                curs = mText.length();
6687            }
6688
6689            if (curs >= 0) {
6690                bringPointIntoView(curs);
6691            }
6692        } else {
6693            bringTextIntoView();
6694        }
6695
6696        // This has to be checked here since:
6697        // - onFocusChanged cannot start it when focus is given to a view with selected text (after
6698        //   a screen rotation) since layout is not yet initialized at that point.
6699        if (mEditor != null && mEditor.mCreatedWithASelection) {
6700            mEditor.refreshTextActionMode();
6701            mEditor.mCreatedWithASelection = false;
6702        }
6703
6704        unregisterForPreDraw();
6705
6706        return true;
6707    }
6708
6709    @Override
6710    protected void onAttachedToWindow() {
6711        super.onAttachedToWindow();
6712
6713        if (mEditor != null) mEditor.onAttachedToWindow();
6714
6715        if (mPreDrawListenerDetached) {
6716            getViewTreeObserver().addOnPreDrawListener(this);
6717            mPreDrawListenerDetached = false;
6718        }
6719    }
6720
6721    /** @hide */
6722    @Override
6723    protected void onDetachedFromWindowInternal() {
6724        if (mPreDrawRegistered) {
6725            getViewTreeObserver().removeOnPreDrawListener(this);
6726            mPreDrawListenerDetached = true;
6727        }
6728
6729        resetResolvedDrawables();
6730
6731        if (mEditor != null) mEditor.onDetachedFromWindow();
6732
6733        super.onDetachedFromWindowInternal();
6734    }
6735
6736    @Override
6737    public void onScreenStateChanged(int screenState) {
6738        super.onScreenStateChanged(screenState);
6739        if (mEditor != null) mEditor.onScreenStateChanged(screenState);
6740    }
6741
6742    @Override
6743    protected boolean isPaddingOffsetRequired() {
6744        return mShadowRadius != 0 || mDrawables != null;
6745    }
6746
6747    @Override
6748    protected int getLeftPaddingOffset() {
6749        return getCompoundPaddingLeft() - mPaddingLeft
6750                + (int) Math.min(0, mShadowDx - mShadowRadius);
6751    }
6752
6753    @Override
6754    protected int getTopPaddingOffset() {
6755        return (int) Math.min(0, mShadowDy - mShadowRadius);
6756    }
6757
6758    @Override
6759    protected int getBottomPaddingOffset() {
6760        return (int) Math.max(0, mShadowDy + mShadowRadius);
6761    }
6762
6763    @Override
6764    protected int getRightPaddingOffset() {
6765        return -(getCompoundPaddingRight() - mPaddingRight)
6766                + (int) Math.max(0, mShadowDx + mShadowRadius);
6767    }
6768
6769    @Override
6770    protected boolean verifyDrawable(@NonNull Drawable who) {
6771        final boolean verified = super.verifyDrawable(who);
6772        if (!verified && mDrawables != null) {
6773            for (Drawable dr : mDrawables.mShowing) {
6774                if (who == dr) {
6775                    return true;
6776                }
6777            }
6778        }
6779        return verified;
6780    }
6781
6782    @Override
6783    public void jumpDrawablesToCurrentState() {
6784        super.jumpDrawablesToCurrentState();
6785        if (mDrawables != null) {
6786            for (Drawable dr : mDrawables.mShowing) {
6787                if (dr != null) {
6788                    dr.jumpToCurrentState();
6789                }
6790            }
6791        }
6792    }
6793
6794    @Override
6795    public void invalidateDrawable(@NonNull Drawable drawable) {
6796        boolean handled = false;
6797
6798        if (verifyDrawable(drawable)) {
6799            final Rect dirty = drawable.getBounds();
6800            int scrollX = mScrollX;
6801            int scrollY = mScrollY;
6802
6803            // IMPORTANT: The coordinates below are based on the coordinates computed
6804            // for each compound drawable in onDraw(). Make sure to update each section
6805            // accordingly.
6806            final TextView.Drawables drawables = mDrawables;
6807            if (drawables != null) {
6808                if (drawable == drawables.mShowing[Drawables.LEFT]) {
6809                    final int compoundPaddingTop = getCompoundPaddingTop();
6810                    final int compoundPaddingBottom = getCompoundPaddingBottom();
6811                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6812
6813                    scrollX += mPaddingLeft;
6814                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
6815                    handled = true;
6816                } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
6817                    final int compoundPaddingTop = getCompoundPaddingTop();
6818                    final int compoundPaddingBottom = getCompoundPaddingBottom();
6819                    final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6820
6821                    scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
6822                    scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
6823                    handled = true;
6824                } else if (drawable == drawables.mShowing[Drawables.TOP]) {
6825                    final int compoundPaddingLeft = getCompoundPaddingLeft();
6826                    final int compoundPaddingRight = getCompoundPaddingRight();
6827                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6828
6829                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
6830                    scrollY += mPaddingTop;
6831                    handled = true;
6832                } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
6833                    final int compoundPaddingLeft = getCompoundPaddingLeft();
6834                    final int compoundPaddingRight = getCompoundPaddingRight();
6835                    final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6836
6837                    scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
6838                    scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
6839                    handled = true;
6840                }
6841            }
6842
6843            if (handled) {
6844                invalidate(dirty.left + scrollX, dirty.top + scrollY,
6845                        dirty.right + scrollX, dirty.bottom + scrollY);
6846            }
6847        }
6848
6849        if (!handled) {
6850            super.invalidateDrawable(drawable);
6851        }
6852    }
6853
6854    @Override
6855    public boolean hasOverlappingRendering() {
6856        // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
6857        return ((getBackground() != null && getBackground().getCurrent() != null)
6858                || mText instanceof Spannable || hasSelection()
6859                || isHorizontalFadingEdgeEnabled());
6860    }
6861
6862    /**
6863     *
6864     * Returns the state of the {@code textIsSelectable} flag (See
6865     * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
6866     * to allow users to select and copy text in a non-editable TextView, the content of an
6867     * {@link EditText} can always be selected, independently of the value of this flag.
6868     * <p>
6869     *
6870     * @return True if the text displayed in this TextView can be selected by the user.
6871     *
6872     * @attr ref android.R.styleable#TextView_textIsSelectable
6873     */
6874    public boolean isTextSelectable() {
6875        return mEditor == null ? false : mEditor.mTextIsSelectable;
6876    }
6877
6878    /**
6879     * Sets whether the content of this view is selectable by the user. The default is
6880     * {@code false}, meaning that the content is not selectable.
6881     * <p>
6882     * When you use a TextView to display a useful piece of information to the user (such as a
6883     * contact's address), make it selectable, so that the user can select and copy its
6884     * content. You can also use set the XML attribute
6885     * {@link android.R.styleable#TextView_textIsSelectable} to "true".
6886     * <p>
6887     * When you call this method to set the value of {@code textIsSelectable}, it sets
6888     * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
6889     * and {@code longClickable} to the same value. These flags correspond to the attributes
6890     * {@link android.R.styleable#View_focusable android:focusable},
6891     * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
6892     * {@link android.R.styleable#View_clickable android:clickable}, and
6893     * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
6894     * flags to a state you had set previously, call one or more of the following methods:
6895     * {@link #setFocusable(boolean) setFocusable()},
6896     * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
6897     * {@link #setClickable(boolean) setClickable()} or
6898     * {@link #setLongClickable(boolean) setLongClickable()}.
6899     *
6900     * @param selectable Whether the content of this TextView should be selectable.
6901     */
6902    public void setTextIsSelectable(boolean selectable) {
6903        if (!selectable && mEditor == null) return; // false is default value with no edit data
6904
6905        createEditorIfNeeded();
6906        if (mEditor.mTextIsSelectable == selectable) return;
6907
6908        mEditor.mTextIsSelectable = selectable;
6909        setFocusableInTouchMode(selectable);
6910        setFocusable(FOCUSABLE_AUTO);
6911        setClickable(selectable);
6912        setLongClickable(selectable);
6913
6914        // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
6915
6916        setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
6917        setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
6918
6919        // Called by setText above, but safer in case of future code changes
6920        mEditor.prepareCursorControllers();
6921    }
6922
6923    @Override
6924    protected int[] onCreateDrawableState(int extraSpace) {
6925        final int[] drawableState;
6926
6927        if (mSingleLine) {
6928            drawableState = super.onCreateDrawableState(extraSpace);
6929        } else {
6930            drawableState = super.onCreateDrawableState(extraSpace + 1);
6931            mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
6932        }
6933
6934        if (isTextSelectable()) {
6935            // Disable pressed state, which was introduced when TextView was made clickable.
6936            // Prevents text color change.
6937            // setClickable(false) would have a similar effect, but it also disables focus changes
6938            // and long press actions, which are both needed by text selection.
6939            final int length = drawableState.length;
6940            for (int i = 0; i < length; i++) {
6941                if (drawableState[i] == R.attr.state_pressed) {
6942                    final int[] nonPressedState = new int[length - 1];
6943                    System.arraycopy(drawableState, 0, nonPressedState, 0, i);
6944                    System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
6945                    return nonPressedState;
6946                }
6947            }
6948        }
6949
6950        return drawableState;
6951    }
6952
6953    private Path getUpdatedHighlightPath() {
6954        Path highlight = null;
6955        Paint highlightPaint = mHighlightPaint;
6956
6957        final int selStart = getSelectionStart();
6958        final int selEnd = getSelectionEnd();
6959        if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
6960            if (selStart == selEnd) {
6961                if (mEditor != null && mEditor.isCursorVisible()
6962                        && (SystemClock.uptimeMillis() - mEditor.mShowCursor)
6963                        % (2 * Editor.BLINK) < Editor.BLINK) {
6964                    if (mHighlightPathBogus) {
6965                        if (mHighlightPath == null) mHighlightPath = new Path();
6966                        mHighlightPath.reset();
6967                        mLayout.getCursorPath(selStart, mHighlightPath, mText);
6968                        mEditor.updateCursorPosition();
6969                        mHighlightPathBogus = false;
6970                    }
6971
6972                    // XXX should pass to skin instead of drawing directly
6973                    highlightPaint.setColor(mCurTextColor);
6974                    highlightPaint.setStyle(Paint.Style.STROKE);
6975                    highlight = mHighlightPath;
6976                }
6977            } else {
6978                if (mHighlightPathBogus) {
6979                    if (mHighlightPath == null) mHighlightPath = new Path();
6980                    mHighlightPath.reset();
6981                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6982                    mHighlightPathBogus = false;
6983                }
6984
6985                // XXX should pass to skin instead of drawing directly
6986                highlightPaint.setColor(mHighlightColor);
6987                highlightPaint.setStyle(Paint.Style.FILL);
6988
6989                highlight = mHighlightPath;
6990            }
6991        }
6992        return highlight;
6993    }
6994
6995    /**
6996     * @hide
6997     */
6998    public int getHorizontalOffsetForDrawables() {
6999        return 0;
7000    }
7001
7002    @Override
7003    protected void onDraw(Canvas canvas) {
7004        restartMarqueeIfNeeded();
7005
7006        // Draw the background for this view
7007        super.onDraw(canvas);
7008
7009        final int compoundPaddingLeft = getCompoundPaddingLeft();
7010        final int compoundPaddingTop = getCompoundPaddingTop();
7011        final int compoundPaddingRight = getCompoundPaddingRight();
7012        final int compoundPaddingBottom = getCompoundPaddingBottom();
7013        final int scrollX = mScrollX;
7014        final int scrollY = mScrollY;
7015        final int right = mRight;
7016        final int left = mLeft;
7017        final int bottom = mBottom;
7018        final int top = mTop;
7019        final boolean isLayoutRtl = isLayoutRtl();
7020        final int offset = getHorizontalOffsetForDrawables();
7021        final int leftOffset = isLayoutRtl ? 0 : offset;
7022        final int rightOffset = isLayoutRtl ? offset : 0;
7023
7024        final Drawables dr = mDrawables;
7025        if (dr != null) {
7026            /*
7027             * Compound, not extended, because the icon is not clipped
7028             * if the text height is smaller.
7029             */
7030
7031            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
7032            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
7033
7034            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7035            // Make sure to update invalidateDrawable() when changing this code.
7036            if (dr.mShowing[Drawables.LEFT] != null) {
7037                canvas.save();
7038                canvas.translate(scrollX + mPaddingLeft + leftOffset,
7039                        scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
7040                dr.mShowing[Drawables.LEFT].draw(canvas);
7041                canvas.restore();
7042            }
7043
7044            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7045            // Make sure to update invalidateDrawable() when changing this code.
7046            if (dr.mShowing[Drawables.RIGHT] != null) {
7047                canvas.save();
7048                canvas.translate(scrollX + right - left - mPaddingRight
7049                        - dr.mDrawableSizeRight - rightOffset,
7050                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
7051                dr.mShowing[Drawables.RIGHT].draw(canvas);
7052                canvas.restore();
7053            }
7054
7055            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7056            // Make sure to update invalidateDrawable() when changing this code.
7057            if (dr.mShowing[Drawables.TOP] != null) {
7058                canvas.save();
7059                canvas.translate(scrollX + compoundPaddingLeft
7060                        + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
7061                dr.mShowing[Drawables.TOP].draw(canvas);
7062                canvas.restore();
7063            }
7064
7065            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
7066            // Make sure to update invalidateDrawable() when changing this code.
7067            if (dr.mShowing[Drawables.BOTTOM] != null) {
7068                canvas.save();
7069                canvas.translate(scrollX + compoundPaddingLeft
7070                        + (hspace - dr.mDrawableWidthBottom) / 2,
7071                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
7072                dr.mShowing[Drawables.BOTTOM].draw(canvas);
7073                canvas.restore();
7074            }
7075        }
7076
7077        int color = mCurTextColor;
7078
7079        if (mLayout == null) {
7080            assumeLayout();
7081        }
7082
7083        Layout layout = mLayout;
7084
7085        if (mHint != null && mText.length() == 0) {
7086            if (mHintTextColor != null) {
7087                color = mCurHintTextColor;
7088            }
7089
7090            layout = mHintLayout;
7091        }
7092
7093        mTextPaint.setColor(color);
7094        mTextPaint.drawableState = getDrawableState();
7095
7096        canvas.save();
7097        /*  Would be faster if we didn't have to do this. Can we chop the
7098            (displayable) text so that we don't need to do this ever?
7099        */
7100
7101        int extendedPaddingTop = getExtendedPaddingTop();
7102        int extendedPaddingBottom = getExtendedPaddingBottom();
7103
7104        final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
7105        final int maxScrollY = mLayout.getHeight() - vspace;
7106
7107        float clipLeft = compoundPaddingLeft + scrollX;
7108        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
7109        float clipRight = right - left - getCompoundPaddingRight() + scrollX;
7110        float clipBottom = bottom - top + scrollY
7111                - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
7112
7113        if (mShadowRadius != 0) {
7114            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
7115            clipRight += Math.max(0, mShadowDx + mShadowRadius);
7116
7117            clipTop += Math.min(0, mShadowDy - mShadowRadius);
7118            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
7119        }
7120
7121        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
7122
7123        int voffsetText = 0;
7124        int voffsetCursor = 0;
7125
7126        // translate in by our padding
7127        /* shortcircuit calling getVerticaOffset() */
7128        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7129            voffsetText = getVerticalOffset(false);
7130            voffsetCursor = getVerticalOffset(true);
7131        }
7132        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
7133
7134        final int layoutDirection = getLayoutDirection();
7135        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
7136        if (isMarqueeFadeEnabled()) {
7137            if (!mSingleLine && getLineCount() == 1 && canMarquee()
7138                    && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
7139                final int width = mRight - mLeft;
7140                final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
7141                final float dx = mLayout.getLineRight(0) - (width - padding);
7142                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7143            }
7144
7145            if (mMarquee != null && mMarquee.isRunning()) {
7146                final float dx = -mMarquee.getScroll();
7147                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7148            }
7149        }
7150
7151        final int cursorOffsetVertical = voffsetCursor - voffsetText;
7152
7153        Path highlight = getUpdatedHighlightPath();
7154        if (mEditor != null) {
7155            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
7156        } else {
7157            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7158        }
7159
7160        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
7161            final float dx = mMarquee.getGhostOffset();
7162            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
7163            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
7164        }
7165
7166        canvas.restore();
7167    }
7168
7169    @Override
7170    public void getFocusedRect(Rect r) {
7171        if (mLayout == null) {
7172            super.getFocusedRect(r);
7173            return;
7174        }
7175
7176        int selEnd = getSelectionEnd();
7177        if (selEnd < 0) {
7178            super.getFocusedRect(r);
7179            return;
7180        }
7181
7182        int selStart = getSelectionStart();
7183        if (selStart < 0 || selStart >= selEnd) {
7184            int line = mLayout.getLineForOffset(selEnd);
7185            r.top = mLayout.getLineTop(line);
7186            r.bottom = mLayout.getLineBottom(line);
7187            r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
7188            r.right = r.left + 4;
7189        } else {
7190            int lineStart = mLayout.getLineForOffset(selStart);
7191            int lineEnd = mLayout.getLineForOffset(selEnd);
7192            r.top = mLayout.getLineTop(lineStart);
7193            r.bottom = mLayout.getLineBottom(lineEnd);
7194            if (lineStart == lineEnd) {
7195                r.left = (int) mLayout.getPrimaryHorizontal(selStart);
7196                r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
7197            } else {
7198                // Selection extends across multiple lines -- make the focused
7199                // rect cover the entire width.
7200                if (mHighlightPathBogus) {
7201                    if (mHighlightPath == null) mHighlightPath = new Path();
7202                    mHighlightPath.reset();
7203                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
7204                    mHighlightPathBogus = false;
7205                }
7206                synchronized (TEMP_RECTF) {
7207                    mHighlightPath.computeBounds(TEMP_RECTF, true);
7208                    r.left = (int) TEMP_RECTF.left - 1;
7209                    r.right = (int) TEMP_RECTF.right + 1;
7210                }
7211            }
7212        }
7213
7214        // Adjust for padding and gravity.
7215        int paddingLeft = getCompoundPaddingLeft();
7216        int paddingTop = getExtendedPaddingTop();
7217        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7218            paddingTop += getVerticalOffset(false);
7219        }
7220        r.offset(paddingLeft, paddingTop);
7221        int paddingBottom = getExtendedPaddingBottom();
7222        r.bottom += paddingBottom;
7223    }
7224
7225    /**
7226     * Return the number of lines of text, or 0 if the internal Layout has not
7227     * been built.
7228     */
7229    public int getLineCount() {
7230        return mLayout != null ? mLayout.getLineCount() : 0;
7231    }
7232
7233    /**
7234     * Return the baseline for the specified line (0...getLineCount() - 1)
7235     * If bounds is not null, return the top, left, right, bottom extents
7236     * of the specified line in it. If the internal Layout has not been built,
7237     * return 0 and set bounds to (0, 0, 0, 0)
7238     * @param line which line to examine (0..getLineCount() - 1)
7239     * @param bounds Optional. If not null, it returns the extent of the line
7240     * @return the Y-coordinate of the baseline
7241     */
7242    public int getLineBounds(int line, Rect bounds) {
7243        if (mLayout == null) {
7244            if (bounds != null) {
7245                bounds.set(0, 0, 0, 0);
7246            }
7247            return 0;
7248        } else {
7249            int baseline = mLayout.getLineBounds(line, bounds);
7250
7251            int voffset = getExtendedPaddingTop();
7252            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7253                voffset += getVerticalOffset(true);
7254            }
7255            if (bounds != null) {
7256                bounds.offset(getCompoundPaddingLeft(), voffset);
7257            }
7258            return baseline + voffset;
7259        }
7260    }
7261
7262    @Override
7263    public int getBaseline() {
7264        if (mLayout == null) {
7265            return super.getBaseline();
7266        }
7267
7268        return getBaselineOffset() + mLayout.getLineBaseline(0);
7269    }
7270
7271    int getBaselineOffset() {
7272        int voffset = 0;
7273        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7274            voffset = getVerticalOffset(true);
7275        }
7276
7277        if (isLayoutModeOptical(mParent)) {
7278            voffset -= getOpticalInsets().top;
7279        }
7280
7281        return getExtendedPaddingTop() + voffset;
7282    }
7283
7284    /**
7285     * @hide
7286     */
7287    @Override
7288    protected int getFadeTop(boolean offsetRequired) {
7289        if (mLayout == null) return 0;
7290
7291        int voffset = 0;
7292        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7293            voffset = getVerticalOffset(true);
7294        }
7295
7296        if (offsetRequired) voffset += getTopPaddingOffset();
7297
7298        return getExtendedPaddingTop() + voffset;
7299    }
7300
7301    /**
7302     * @hide
7303     */
7304    @Override
7305    protected int getFadeHeight(boolean offsetRequired) {
7306        return mLayout != null ? mLayout.getHeight() : 0;
7307    }
7308
7309    @Override
7310    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
7311        if (mText instanceof Spannable && mLinksClickable) {
7312            final float x = event.getX(pointerIndex);
7313            final float y = event.getY(pointerIndex);
7314            final int offset = getOffsetForPosition(x, y);
7315            final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
7316                    ClickableSpan.class);
7317            if (clickables.length > 0) {
7318                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
7319            }
7320        }
7321        if (isTextSelectable() || isTextEditable()) {
7322            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
7323        }
7324        return super.onResolvePointerIcon(event, pointerIndex);
7325    }
7326
7327    @Override
7328    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
7329        // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
7330        // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
7331        // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
7332        if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
7333            return true;
7334        }
7335        return super.onKeyPreIme(keyCode, event);
7336    }
7337
7338    /**
7339     * @hide
7340     */
7341    public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
7342        // Do nothing unless mEditor is in text action mode.
7343        if (mEditor == null || mEditor.getTextActionMode() == null) {
7344            return false;
7345        }
7346
7347        if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
7348            KeyEvent.DispatcherState state = getKeyDispatcherState();
7349            if (state != null) {
7350                state.startTracking(event, this);
7351            }
7352            return true;
7353        } else if (event.getAction() == KeyEvent.ACTION_UP) {
7354            KeyEvent.DispatcherState state = getKeyDispatcherState();
7355            if (state != null) {
7356                state.handleUpEvent(event);
7357            }
7358            if (event.isTracking() && !event.isCanceled()) {
7359                stopTextActionMode();
7360                return true;
7361            }
7362        }
7363        return false;
7364    }
7365
7366    @Override
7367    public boolean onKeyDown(int keyCode, KeyEvent event) {
7368        final int which = doKeyDown(keyCode, event, null);
7369        if (which == KEY_EVENT_NOT_HANDLED) {
7370            return super.onKeyDown(keyCode, event);
7371        }
7372
7373        return true;
7374    }
7375
7376    @Override
7377    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
7378        KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
7379        final int which = doKeyDown(keyCode, down, event);
7380        if (which == KEY_EVENT_NOT_HANDLED) {
7381            // Go through default dispatching.
7382            return super.onKeyMultiple(keyCode, repeatCount, event);
7383        }
7384        if (which == KEY_EVENT_HANDLED) {
7385            // Consumed the whole thing.
7386            return true;
7387        }
7388
7389        repeatCount--;
7390
7391        // We are going to dispatch the remaining events to either the input
7392        // or movement method.  To do this, we will just send a repeated stream
7393        // of down and up events until we have done the complete repeatCount.
7394        // It would be nice if those interfaces had an onKeyMultiple() method,
7395        // but adding that is a more complicated change.
7396        KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
7397        if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
7398            // mEditor and mEditor.mInput are not null from doKeyDown
7399            mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7400            while (--repeatCount > 0) {
7401                mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
7402                mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7403            }
7404            hideErrorIfUnchanged();
7405
7406        } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
7407            // mMovement is not null from doKeyDown
7408            mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
7409            while (--repeatCount > 0) {
7410                mMovement.onKeyDown(this, (Spannable) mText, keyCode, down);
7411                mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
7412            }
7413        }
7414
7415        return true;
7416    }
7417
7418    /**
7419     * Returns true if pressing ENTER in this field advances focus instead
7420     * of inserting the character.  This is true mostly in single-line fields,
7421     * but also in mail addresses and subjects which will display on multiple
7422     * lines but where it doesn't make sense to insert newlines.
7423     */
7424    private boolean shouldAdvanceFocusOnEnter() {
7425        if (getKeyListener() == null) {
7426            return false;
7427        }
7428
7429        if (mSingleLine) {
7430            return true;
7431        }
7432
7433        if (mEditor != null
7434                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7435                        == EditorInfo.TYPE_CLASS_TEXT) {
7436            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7437            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
7438                    || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
7439                return true;
7440            }
7441        }
7442
7443        return false;
7444    }
7445
7446    /**
7447     * Returns true if pressing TAB in this field advances focus instead
7448     * of inserting the character.  Insert tabs only in multi-line editors.
7449     */
7450    private boolean shouldAdvanceFocusOnTab() {
7451        if (getKeyListener() != null && !mSingleLine && mEditor != null
7452                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7453                        == EditorInfo.TYPE_CLASS_TEXT) {
7454            int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7455            if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
7456                    || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
7457                return false;
7458            }
7459        }
7460        return true;
7461    }
7462
7463    private boolean isDirectionalNavigationKey(int keyCode) {
7464        switch(keyCode) {
7465            case KeyEvent.KEYCODE_DPAD_UP:
7466            case KeyEvent.KEYCODE_DPAD_DOWN:
7467            case KeyEvent.KEYCODE_DPAD_LEFT:
7468            case KeyEvent.KEYCODE_DPAD_RIGHT:
7469                return true;
7470        }
7471        return false;
7472    }
7473
7474    private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
7475        if (!isEnabled()) {
7476            return KEY_EVENT_NOT_HANDLED;
7477        }
7478
7479        // If this is the initial keydown, we don't want to prevent a movement away from this view.
7480        // While this shouldn't be necessary because any time we're preventing default movement we
7481        // should be restricting the focus to remain within this view, thus we'll also receive
7482        // the key up event, occasionally key up events will get dropped and we don't want to
7483        // prevent the user from traversing out of this on the next key down.
7484        if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7485            mPreventDefaultMovement = false;
7486        }
7487
7488        switch (keyCode) {
7489            case KeyEvent.KEYCODE_ENTER:
7490                if (event.hasNoModifiers()) {
7491                    // When mInputContentType is set, we know that we are
7492                    // running in a "modern" cupcake environment, so don't need
7493                    // to worry about the application trying to capture
7494                    // enter key events.
7495                    if (mEditor != null && mEditor.mInputContentType != null) {
7496                        // If there is an action listener, given them a
7497                        // chance to consume the event.
7498                        if (mEditor.mInputContentType.onEditorActionListener != null
7499                                && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7500                                        this, EditorInfo.IME_NULL, event)) {
7501                            mEditor.mInputContentType.enterDown = true;
7502                            // We are consuming the enter key for them.
7503                            return KEY_EVENT_HANDLED;
7504                        }
7505                    }
7506
7507                    // If our editor should move focus when enter is pressed, or
7508                    // this is a generated event from an IME action button, then
7509                    // don't let it be inserted into the text.
7510                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7511                            || shouldAdvanceFocusOnEnter()) {
7512                        if (hasOnClickListeners()) {
7513                            return KEY_EVENT_NOT_HANDLED;
7514                        }
7515                        return KEY_EVENT_HANDLED;
7516                    }
7517                }
7518                break;
7519
7520            case KeyEvent.KEYCODE_DPAD_CENTER:
7521                if (event.hasNoModifiers()) {
7522                    if (shouldAdvanceFocusOnEnter()) {
7523                        return KEY_EVENT_NOT_HANDLED;
7524                    }
7525                }
7526                break;
7527
7528            case KeyEvent.KEYCODE_TAB:
7529                if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
7530                    if (shouldAdvanceFocusOnTab()) {
7531                        return KEY_EVENT_NOT_HANDLED;
7532                    }
7533                }
7534                break;
7535
7536                // Has to be done on key down (and not on key up) to correctly be intercepted.
7537            case KeyEvent.KEYCODE_BACK:
7538                if (mEditor != null && mEditor.getTextActionMode() != null) {
7539                    stopTextActionMode();
7540                    return KEY_EVENT_HANDLED;
7541                }
7542                break;
7543
7544            case KeyEvent.KEYCODE_CUT:
7545                if (event.hasNoModifiers() && canCut()) {
7546                    if (onTextContextMenuItem(ID_CUT)) {
7547                        return KEY_EVENT_HANDLED;
7548                    }
7549                }
7550                break;
7551
7552            case KeyEvent.KEYCODE_COPY:
7553                if (event.hasNoModifiers() && canCopy()) {
7554                    if (onTextContextMenuItem(ID_COPY)) {
7555                        return KEY_EVENT_HANDLED;
7556                    }
7557                }
7558                break;
7559
7560            case KeyEvent.KEYCODE_PASTE:
7561                if (event.hasNoModifiers() && canPaste()) {
7562                    if (onTextContextMenuItem(ID_PASTE)) {
7563                        return KEY_EVENT_HANDLED;
7564                    }
7565                }
7566                break;
7567        }
7568
7569        if (mEditor != null && mEditor.mKeyListener != null) {
7570            boolean doDown = true;
7571            if (otherEvent != null) {
7572                try {
7573                    beginBatchEdit();
7574                    final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
7575                            otherEvent);
7576                    hideErrorIfUnchanged();
7577                    doDown = false;
7578                    if (handled) {
7579                        return KEY_EVENT_HANDLED;
7580                    }
7581                } catch (AbstractMethodError e) {
7582                    // onKeyOther was added after 1.0, so if it isn't
7583                    // implemented we need to try to dispatch as a regular down.
7584                } finally {
7585                    endBatchEdit();
7586                }
7587            }
7588
7589            if (doDown) {
7590                beginBatchEdit();
7591                final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
7592                        keyCode, event);
7593                endBatchEdit();
7594                hideErrorIfUnchanged();
7595                if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
7596            }
7597        }
7598
7599        // bug 650865: sometimes we get a key event before a layout.
7600        // don't try to move around if we don't know the layout.
7601
7602        if (mMovement != null && mLayout != null) {
7603            boolean doDown = true;
7604            if (otherEvent != null) {
7605                try {
7606                    boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
7607                            otherEvent);
7608                    doDown = false;
7609                    if (handled) {
7610                        return KEY_EVENT_HANDLED;
7611                    }
7612                } catch (AbstractMethodError e) {
7613                    // onKeyOther was added after 1.0, so if it isn't
7614                    // implemented we need to try to dispatch as a regular down.
7615                }
7616            }
7617            if (doDown) {
7618                if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) {
7619                    if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7620                        mPreventDefaultMovement = true;
7621                    }
7622                    return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
7623                }
7624            }
7625            // Consume arrows from keyboard devices to prevent focus leaving the editor.
7626            // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
7627            // to move focus with arrows.
7628            if (event.getSource() == InputDevice.SOURCE_KEYBOARD
7629                    && isDirectionalNavigationKey(keyCode)) {
7630                return KEY_EVENT_HANDLED;
7631            }
7632        }
7633
7634        return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
7635                ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
7636    }
7637
7638    /**
7639     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
7640     * can be recorded.
7641     * @hide
7642     */
7643    public void resetErrorChangedFlag() {
7644        /*
7645         * Keep track of what the error was before doing the input
7646         * so that if an input filter changed the error, we leave
7647         * that error showing.  Otherwise, we take down whatever
7648         * error was showing when the user types something.
7649         */
7650        if (mEditor != null) mEditor.mErrorWasChanged = false;
7651    }
7652
7653    /**
7654     * @hide
7655     */
7656    public void hideErrorIfUnchanged() {
7657        if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
7658            setError(null, null);
7659        }
7660    }
7661
7662    @Override
7663    public boolean onKeyUp(int keyCode, KeyEvent event) {
7664        if (!isEnabled()) {
7665            return super.onKeyUp(keyCode, event);
7666        }
7667
7668        if (!KeyEvent.isModifierKey(keyCode)) {
7669            mPreventDefaultMovement = false;
7670        }
7671
7672        switch (keyCode) {
7673            case KeyEvent.KEYCODE_DPAD_CENTER:
7674                if (event.hasNoModifiers()) {
7675                    /*
7676                     * If there is a click listener, just call through to
7677                     * super, which will invoke it.
7678                     *
7679                     * If there isn't a click listener, try to show the soft
7680                     * input method.  (It will also
7681                     * call performClick(), but that won't do anything in
7682                     * this case.)
7683                     */
7684                    if (!hasOnClickListeners()) {
7685                        if (mMovement != null && mText instanceof Editable
7686                                && mLayout != null && onCheckIsTextEditor()) {
7687                            InputMethodManager imm = InputMethodManager.peekInstance();
7688                            viewClicked(imm);
7689                            if (imm != null && getShowSoftInputOnFocus()) {
7690                                imm.showSoftInput(this, 0);
7691                            }
7692                        }
7693                    }
7694                }
7695                return super.onKeyUp(keyCode, event);
7696
7697            case KeyEvent.KEYCODE_ENTER:
7698                if (event.hasNoModifiers()) {
7699                    if (mEditor != null && mEditor.mInputContentType != null
7700                            && mEditor.mInputContentType.onEditorActionListener != null
7701                            && mEditor.mInputContentType.enterDown) {
7702                        mEditor.mInputContentType.enterDown = false;
7703                        if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7704                                this, EditorInfo.IME_NULL, event)) {
7705                            return true;
7706                        }
7707                    }
7708
7709                    if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7710                            || shouldAdvanceFocusOnEnter()) {
7711                        /*
7712                         * If there is a click listener, just call through to
7713                         * super, which will invoke it.
7714                         *
7715                         * If there isn't a click listener, try to advance focus,
7716                         * but still call through to super, which will reset the
7717                         * pressed state and longpress state.  (It will also
7718                         * call performClick(), but that won't do anything in
7719                         * this case.)
7720                         */
7721                        if (!hasOnClickListeners()) {
7722                            View v = focusSearch(FOCUS_DOWN);
7723
7724                            if (v != null) {
7725                                if (!v.requestFocus(FOCUS_DOWN)) {
7726                                    throw new IllegalStateException("focus search returned a view "
7727                                            + "that wasn't able to take focus!");
7728                                }
7729
7730                                /*
7731                                 * Return true because we handled the key; super
7732                                 * will return false because there was no click
7733                                 * listener.
7734                                 */
7735                                super.onKeyUp(keyCode, event);
7736                                return true;
7737                            } else if ((event.getFlags()
7738                                    & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
7739                                // No target for next focus, but make sure the IME
7740                                // if this came from it.
7741                                InputMethodManager imm = InputMethodManager.peekInstance();
7742                                if (imm != null && imm.isActive(this)) {
7743                                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
7744                                }
7745                            }
7746                        }
7747                    }
7748                    return super.onKeyUp(keyCode, event);
7749                }
7750                break;
7751        }
7752
7753        if (mEditor != null && mEditor.mKeyListener != null) {
7754            if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
7755                return true;
7756            }
7757        }
7758
7759        if (mMovement != null && mLayout != null) {
7760            if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) {
7761                return true;
7762            }
7763        }
7764
7765        return super.onKeyUp(keyCode, event);
7766    }
7767
7768    @Override
7769    public boolean onCheckIsTextEditor() {
7770        return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
7771    }
7772
7773    @Override
7774    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
7775        if (onCheckIsTextEditor() && isEnabled()) {
7776            mEditor.createInputMethodStateIfNeeded();
7777            outAttrs.inputType = getInputType();
7778            if (mEditor.mInputContentType != null) {
7779                outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
7780                outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
7781                outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
7782                outAttrs.actionId = mEditor.mInputContentType.imeActionId;
7783                outAttrs.extras = mEditor.mInputContentType.extras;
7784                outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
7785            } else {
7786                outAttrs.imeOptions = EditorInfo.IME_NULL;
7787                outAttrs.hintLocales = null;
7788            }
7789            if (focusSearch(FOCUS_DOWN) != null) {
7790                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
7791            }
7792            if (focusSearch(FOCUS_UP) != null) {
7793                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
7794            }
7795            if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
7796                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
7797                if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
7798                    // An action has not been set, but the enter key will move to
7799                    // the next focus, so set the action to that.
7800                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
7801                } else {
7802                    // An action has not been set, and there is no focus to move
7803                    // to, so let's just supply a "done" action.
7804                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
7805                }
7806                if (!shouldAdvanceFocusOnEnter()) {
7807                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7808                }
7809            }
7810            if (isMultilineInputType(outAttrs.inputType)) {
7811                // Multi-line text editors should always show an enter key.
7812                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7813            }
7814            outAttrs.hintText = mHint;
7815            if (mText instanceof Editable) {
7816                InputConnection ic = new EditableInputConnection(this);
7817                outAttrs.initialSelStart = getSelectionStart();
7818                outAttrs.initialSelEnd = getSelectionEnd();
7819                outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
7820                return ic;
7821            }
7822        }
7823        return null;
7824    }
7825
7826    /**
7827     * If this TextView contains editable content, extract a portion of it
7828     * based on the information in <var>request</var> in to <var>outText</var>.
7829     * @return Returns true if the text was successfully extracted, else false.
7830     */
7831    public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
7832        createEditorIfNeeded();
7833        return mEditor.extractText(request, outText);
7834    }
7835
7836    /**
7837     * This is used to remove all style-impacting spans from text before new
7838     * extracted text is being replaced into it, so that we don't have any
7839     * lingering spans applied during the replace.
7840     */
7841    static void removeParcelableSpans(Spannable spannable, int start, int end) {
7842        Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
7843        int i = spans.length;
7844        while (i > 0) {
7845            i--;
7846            spannable.removeSpan(spans[i]);
7847        }
7848    }
7849
7850    /**
7851     * Apply to this text view the given extracted text, as previously
7852     * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
7853     */
7854    public void setExtractedText(ExtractedText text) {
7855        Editable content = getEditableText();
7856        if (text.text != null) {
7857            if (content == null) {
7858                setText(text.text, TextView.BufferType.EDITABLE);
7859            } else {
7860                int start = 0;
7861                int end = content.length();
7862
7863                if (text.partialStartOffset >= 0) {
7864                    final int N = content.length();
7865                    start = text.partialStartOffset;
7866                    if (start > N) start = N;
7867                    end = text.partialEndOffset;
7868                    if (end > N) end = N;
7869                }
7870
7871                removeParcelableSpans(content, start, end);
7872                if (TextUtils.equals(content.subSequence(start, end), text.text)) {
7873                    if (text.text instanceof Spanned) {
7874                        // OK to copy spans only.
7875                        TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
7876                                Object.class, content, start);
7877                    }
7878                } else {
7879                    content.replace(start, end, text.text);
7880                }
7881            }
7882        }
7883
7884        // Now set the selection position...  make sure it is in range, to
7885        // avoid crashes.  If this is a partial update, it is possible that
7886        // the underlying text may have changed, causing us problems here.
7887        // Also we just don't want to trust clients to do the right thing.
7888        Spannable sp = (Spannable) getText();
7889        final int N = sp.length();
7890        int start = text.selectionStart;
7891        if (start < 0) {
7892            start = 0;
7893        } else if (start > N) {
7894            start = N;
7895        }
7896        int end = text.selectionEnd;
7897        if (end < 0) {
7898            end = 0;
7899        } else if (end > N) {
7900            end = N;
7901        }
7902        Selection.setSelection(sp, start, end);
7903
7904        // Finally, update the selection mode.
7905        if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
7906            MetaKeyKeyListener.startSelecting(this, sp);
7907        } else {
7908            MetaKeyKeyListener.stopSelecting(this, sp);
7909        }
7910
7911        setHintInternal(text.hint);
7912    }
7913
7914    /**
7915     * @hide
7916     */
7917    public void setExtracting(ExtractedTextRequest req) {
7918        if (mEditor.mInputMethodState != null) {
7919            mEditor.mInputMethodState.mExtractedTextRequest = req;
7920        }
7921        // This would stop a possible selection mode, but no such mode is started in case
7922        // extracted mode will start. Some text is selected though, and will trigger an action mode
7923        // in the extracted view.
7924        mEditor.hideCursorAndSpanControllers();
7925        stopTextActionMode();
7926        if (mEditor.mSelectionModifierCursorController != null) {
7927            mEditor.mSelectionModifierCursorController.resetTouchOffsets();
7928        }
7929    }
7930
7931    /**
7932     * Called by the framework in response to a text completion from
7933     * the current input method, provided by it calling
7934     * {@link InputConnection#commitCompletion
7935     * InputConnection.commitCompletion()}.  The default implementation does
7936     * nothing; text views that are supporting auto-completion should override
7937     * this to do their desired behavior.
7938     *
7939     * @param text The auto complete text the user has selected.
7940     */
7941    public void onCommitCompletion(CompletionInfo text) {
7942        // intentionally empty
7943    }
7944
7945    /**
7946     * Called by the framework in response to a text auto-correction (such as fixing a typo using a
7947     * dictionary) from the current input method, provided by it calling
7948     * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
7949     * The default implementation flashes the background of the corrected word to provide
7950     * feedback to the user.
7951     *
7952     * @param info The auto correct info about the text that was corrected.
7953     */
7954    public void onCommitCorrection(CorrectionInfo info) {
7955        if (mEditor != null) mEditor.onCommitCorrection(info);
7956    }
7957
7958    public void beginBatchEdit() {
7959        if (mEditor != null) mEditor.beginBatchEdit();
7960    }
7961
7962    public void endBatchEdit() {
7963        if (mEditor != null) mEditor.endBatchEdit();
7964    }
7965
7966    /**
7967     * Called by the framework in response to a request to begin a batch
7968     * of edit operations through a call to link {@link #beginBatchEdit()}.
7969     */
7970    public void onBeginBatchEdit() {
7971        // intentionally empty
7972    }
7973
7974    /**
7975     * Called by the framework in response to a request to end a batch
7976     * of edit operations through a call to link {@link #endBatchEdit}.
7977     */
7978    public void onEndBatchEdit() {
7979        // intentionally empty
7980    }
7981
7982    /**
7983     * Called by the framework in response to a private command from the
7984     * current method, provided by it calling
7985     * {@link InputConnection#performPrivateCommand
7986     * InputConnection.performPrivateCommand()}.
7987     *
7988     * @param action The action name of the command.
7989     * @param data Any additional data for the command.  This may be null.
7990     * @return Return true if you handled the command, else false.
7991     */
7992    public boolean onPrivateIMECommand(String action, Bundle data) {
7993        return false;
7994    }
7995
7996    private void nullLayouts() {
7997        if (mLayout instanceof BoringLayout && mSavedLayout == null) {
7998            mSavedLayout = (BoringLayout) mLayout;
7999        }
8000        if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
8001            mSavedHintLayout = (BoringLayout) mHintLayout;
8002        }
8003
8004        mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
8005
8006        mBoring = mHintBoring = null;
8007
8008        // Since it depends on the value of mLayout
8009        if (mEditor != null) mEditor.prepareCursorControllers();
8010    }
8011
8012    /**
8013     * Make a new Layout based on the already-measured size of the view,
8014     * on the assumption that it was measured correctly at some point.
8015     */
8016    private void assumeLayout() {
8017        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8018
8019        if (width < 1) {
8020            width = 0;
8021        }
8022
8023        int physicalWidth = width;
8024
8025        if (mHorizontallyScrolling) {
8026            width = VERY_WIDE;
8027        }
8028
8029        makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
8030                      physicalWidth, false);
8031    }
8032
8033    private Layout.Alignment getLayoutAlignment() {
8034        Layout.Alignment alignment;
8035        switch (getTextAlignment()) {
8036            case TEXT_ALIGNMENT_GRAVITY:
8037                switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
8038                    case Gravity.START:
8039                        alignment = Layout.Alignment.ALIGN_NORMAL;
8040                        break;
8041                    case Gravity.END:
8042                        alignment = Layout.Alignment.ALIGN_OPPOSITE;
8043                        break;
8044                    case Gravity.LEFT:
8045                        alignment = Layout.Alignment.ALIGN_LEFT;
8046                        break;
8047                    case Gravity.RIGHT:
8048                        alignment = Layout.Alignment.ALIGN_RIGHT;
8049                        break;
8050                    case Gravity.CENTER_HORIZONTAL:
8051                        alignment = Layout.Alignment.ALIGN_CENTER;
8052                        break;
8053                    default:
8054                        alignment = Layout.Alignment.ALIGN_NORMAL;
8055                        break;
8056                }
8057                break;
8058            case TEXT_ALIGNMENT_TEXT_START:
8059                alignment = Layout.Alignment.ALIGN_NORMAL;
8060                break;
8061            case TEXT_ALIGNMENT_TEXT_END:
8062                alignment = Layout.Alignment.ALIGN_OPPOSITE;
8063                break;
8064            case TEXT_ALIGNMENT_CENTER:
8065                alignment = Layout.Alignment.ALIGN_CENTER;
8066                break;
8067            case TEXT_ALIGNMENT_VIEW_START:
8068                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8069                        ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8070                break;
8071            case TEXT_ALIGNMENT_VIEW_END:
8072                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
8073                        ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8074                break;
8075            case TEXT_ALIGNMENT_INHERIT:
8076                // This should never happen as we have already resolved the text alignment
8077                // but better safe than sorry so we just fall through
8078            default:
8079                alignment = Layout.Alignment.ALIGN_NORMAL;
8080                break;
8081        }
8082        return alignment;
8083    }
8084
8085    /**
8086     * The width passed in is now the desired layout width,
8087     * not the full view width with padding.
8088     * {@hide}
8089     */
8090    protected void makeNewLayout(int wantWidth, int hintWidth,
8091                                 BoringLayout.Metrics boring,
8092                                 BoringLayout.Metrics hintBoring,
8093                                 int ellipsisWidth, boolean bringIntoView) {
8094        stopMarquee();
8095
8096        // Update "old" cached values
8097        mOldMaximum = mMaximum;
8098        mOldMaxMode = mMaxMode;
8099
8100        mHighlightPathBogus = true;
8101
8102        if (wantWidth < 0) {
8103            wantWidth = 0;
8104        }
8105        if (hintWidth < 0) {
8106            hintWidth = 0;
8107        }
8108
8109        Layout.Alignment alignment = getLayoutAlignment();
8110        final boolean testDirChange = mSingleLine && mLayout != null
8111                && (alignment == Layout.Alignment.ALIGN_NORMAL
8112                        || alignment == Layout.Alignment.ALIGN_OPPOSITE);
8113        int oldDir = 0;
8114        if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
8115        boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
8116        final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
8117                && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
8118        TruncateAt effectiveEllipsize = mEllipsize;
8119        if (mEllipsize == TruncateAt.MARQUEE
8120                && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8121            effectiveEllipsize = TruncateAt.END_SMALL;
8122        }
8123
8124        if (mTextDir == null) {
8125            mTextDir = getTextDirectionHeuristic();
8126        }
8127
8128        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
8129                effectiveEllipsize, effectiveEllipsize == mEllipsize);
8130        if (switchEllipsize) {
8131            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
8132                    ? TruncateAt.END : TruncateAt.MARQUEE;
8133            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
8134                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
8135        }
8136
8137        shouldEllipsize = mEllipsize != null;
8138        mHintLayout = null;
8139
8140        if (mHint != null) {
8141            if (shouldEllipsize) hintWidth = wantWidth;
8142
8143            if (hintBoring == UNKNOWN_BORING) {
8144                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
8145                                                   mHintBoring);
8146                if (hintBoring != null) {
8147                    mHintBoring = hintBoring;
8148                }
8149            }
8150
8151            if (hintBoring != null) {
8152                if (hintBoring.width <= hintWidth
8153                        && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
8154                    if (mSavedHintLayout != null) {
8155                        mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
8156                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
8157                                hintBoring, mIncludePad);
8158                    } else {
8159                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
8160                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
8161                                hintBoring, mIncludePad);
8162                    }
8163
8164                    mSavedHintLayout = (BoringLayout) mHintLayout;
8165                } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
8166                    if (mSavedHintLayout != null) {
8167                        mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
8168                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
8169                                hintBoring, mIncludePad, mEllipsize,
8170                                ellipsisWidth);
8171                    } else {
8172                        mHintLayout = BoringLayout.make(mHint, mTextPaint,
8173                                hintWidth, alignment, mSpacingMult, mSpacingAdd,
8174                                hintBoring, mIncludePad, mEllipsize,
8175                                ellipsisWidth);
8176                    }
8177                }
8178            }
8179            // TODO: code duplication with makeSingleLayout()
8180            if (mHintLayout == null) {
8181                StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
8182                        mHint.length(), mTextPaint, hintWidth)
8183                        .setAlignment(alignment)
8184                        .setTextDirection(mTextDir)
8185                        .setLineSpacing(mSpacingAdd, mSpacingMult)
8186                        .setIncludePad(mIncludePad)
8187                        .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8188                        .setBreakStrategy(mBreakStrategy)
8189                        .setHyphenationFrequency(mHyphenationFrequency)
8190                        .setJustificationMode(mJustificationMode)
8191                        .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8192                if (shouldEllipsize) {
8193                    builder.setEllipsize(mEllipsize)
8194                            .setEllipsizedWidth(ellipsisWidth);
8195                }
8196                mHintLayout = builder.build();
8197            }
8198        }
8199
8200        if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
8201            registerForPreDraw();
8202        }
8203
8204        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8205            if (!compressText(ellipsisWidth)) {
8206                final int height = mLayoutParams.height;
8207                // If the size of the view does not depend on the size of the text, try to
8208                // start the marquee immediately
8209                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
8210                    startMarquee();
8211                } else {
8212                    // Defer the start of the marquee until we know our width (see setFrame())
8213                    mRestartMarquee = true;
8214                }
8215            }
8216        }
8217
8218        // CursorControllers need a non-null mLayout
8219        if (mEditor != null) mEditor.prepareCursorControllers();
8220    }
8221
8222    /**
8223     * @hide
8224     */
8225    protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
8226            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
8227            boolean useSaved) {
8228        Layout result = null;
8229        if (mText instanceof Spannable) {
8230            final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
8231                    wantWidth)
8232                    .setDisplayText(mTransformed)
8233                    .setAlignment(alignment)
8234                    .setTextDirection(mTextDir)
8235                    .setLineSpacing(mSpacingAdd, mSpacingMult)
8236                    .setIncludePad(mIncludePad)
8237                    .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8238                    .setBreakStrategy(mBreakStrategy)
8239                    .setHyphenationFrequency(mHyphenationFrequency)
8240                    .setJustificationMode(mJustificationMode)
8241                    .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
8242                    .setEllipsizedWidth(ellipsisWidth);
8243            result = builder.build();
8244        } else {
8245            if (boring == UNKNOWN_BORING) {
8246                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8247                if (boring != null) {
8248                    mBoring = boring;
8249                }
8250            }
8251
8252            if (boring != null) {
8253                if (boring.width <= wantWidth
8254                        && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
8255                    if (useSaved && mSavedLayout != null) {
8256                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
8257                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
8258                                boring, mIncludePad);
8259                    } else {
8260                        result = BoringLayout.make(mTransformed, mTextPaint,
8261                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
8262                                boring, mIncludePad);
8263                    }
8264
8265                    if (useSaved) {
8266                        mSavedLayout = (BoringLayout) result;
8267                    }
8268                } else if (shouldEllipsize && boring.width <= wantWidth) {
8269                    if (useSaved && mSavedLayout != null) {
8270                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
8271                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
8272                                boring, mIncludePad, effectiveEllipsize,
8273                                ellipsisWidth);
8274                    } else {
8275                        result = BoringLayout.make(mTransformed, mTextPaint,
8276                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
8277                                boring, mIncludePad, effectiveEllipsize,
8278                                ellipsisWidth);
8279                    }
8280                }
8281            }
8282        }
8283        if (result == null) {
8284            StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
8285                    0, mTransformed.length(), mTextPaint, wantWidth)
8286                    .setAlignment(alignment)
8287                    .setTextDirection(mTextDir)
8288                    .setLineSpacing(mSpacingAdd, mSpacingMult)
8289                    .setIncludePad(mIncludePad)
8290                    .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8291                    .setBreakStrategy(mBreakStrategy)
8292                    .setHyphenationFrequency(mHyphenationFrequency)
8293                    .setJustificationMode(mJustificationMode)
8294                    .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8295            if (shouldEllipsize) {
8296                builder.setEllipsize(effectiveEllipsize)
8297                        .setEllipsizedWidth(ellipsisWidth);
8298            }
8299            result = builder.build();
8300        }
8301        return result;
8302    }
8303
8304    private boolean compressText(float width) {
8305        if (isHardwareAccelerated()) return false;
8306
8307        // Only compress the text if it hasn't been compressed by the previous pass
8308        if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
8309                && mTextPaint.getTextScaleX() == 1.0f) {
8310            final float textWidth = mLayout.getLineWidth(0);
8311            final float overflow = (textWidth + 1.0f - width) / width;
8312            if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
8313                mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
8314                post(new Runnable() {
8315                    public void run() {
8316                        requestLayout();
8317                    }
8318                });
8319                return true;
8320            }
8321        }
8322
8323        return false;
8324    }
8325
8326    private static int desired(Layout layout) {
8327        int n = layout.getLineCount();
8328        CharSequence text = layout.getText();
8329        float max = 0;
8330
8331        // if any line was wrapped, we can't use it.
8332        // but it's ok for the last line not to have a newline
8333
8334        for (int i = 0; i < n - 1; i++) {
8335            if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
8336                return -1;
8337            }
8338        }
8339
8340        for (int i = 0; i < n; i++) {
8341            max = Math.max(max, layout.getLineWidth(i));
8342        }
8343
8344        return (int) Math.ceil(max);
8345    }
8346
8347    /**
8348     * Set whether the TextView includes extra top and bottom padding to make
8349     * room for accents that go above the normal ascent and descent.
8350     * The default is true.
8351     *
8352     * @see #getIncludeFontPadding()
8353     *
8354     * @attr ref android.R.styleable#TextView_includeFontPadding
8355     */
8356    public void setIncludeFontPadding(boolean includepad) {
8357        if (mIncludePad != includepad) {
8358            mIncludePad = includepad;
8359
8360            if (mLayout != null) {
8361                nullLayouts();
8362                requestLayout();
8363                invalidate();
8364            }
8365        }
8366    }
8367
8368    /**
8369     * Gets whether the TextView includes extra top and bottom padding to make
8370     * room for accents that go above the normal ascent and descent.
8371     *
8372     * @see #setIncludeFontPadding(boolean)
8373     *
8374     * @attr ref android.R.styleable#TextView_includeFontPadding
8375     */
8376    public boolean getIncludeFontPadding() {
8377        return mIncludePad;
8378    }
8379
8380    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
8381
8382    @Override
8383    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
8384        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
8385        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
8386        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
8387        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
8388
8389        int width;
8390        int height;
8391
8392        BoringLayout.Metrics boring = UNKNOWN_BORING;
8393        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
8394
8395        if (mTextDir == null) {
8396            mTextDir = getTextDirectionHeuristic();
8397        }
8398
8399        int des = -1;
8400        boolean fromexisting = false;
8401        final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
8402                ?  (float) widthSize : Float.MAX_VALUE;
8403
8404        if (widthMode == MeasureSpec.EXACTLY) {
8405            // Parent has told us how big to be. So be it.
8406            width = widthSize;
8407        } else {
8408            if (mLayout != null && mEllipsize == null) {
8409                des = desired(mLayout);
8410            }
8411
8412            if (des < 0) {
8413                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8414                if (boring != null) {
8415                    mBoring = boring;
8416                }
8417            } else {
8418                fromexisting = true;
8419            }
8420
8421            if (boring == null || boring == UNKNOWN_BORING) {
8422                if (des < 0) {
8423                    des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
8424                            mTransformed.length(), mTextPaint, mTextDir, widthLimit));
8425                }
8426                width = des;
8427            } else {
8428                width = boring.width;
8429            }
8430
8431            final Drawables dr = mDrawables;
8432            if (dr != null) {
8433                width = Math.max(width, dr.mDrawableWidthTop);
8434                width = Math.max(width, dr.mDrawableWidthBottom);
8435            }
8436
8437            if (mHint != null) {
8438                int hintDes = -1;
8439                int hintWidth;
8440
8441                if (mHintLayout != null && mEllipsize == null) {
8442                    hintDes = desired(mHintLayout);
8443                }
8444
8445                if (hintDes < 0) {
8446                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
8447                    if (hintBoring != null) {
8448                        mHintBoring = hintBoring;
8449                    }
8450                }
8451
8452                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
8453                    if (hintDes < 0) {
8454                        hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
8455                                mHint.length(), mTextPaint, mTextDir, widthLimit));
8456                    }
8457                    hintWidth = hintDes;
8458                } else {
8459                    hintWidth = hintBoring.width;
8460                }
8461
8462                if (hintWidth > width) {
8463                    width = hintWidth;
8464                }
8465            }
8466
8467            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
8468
8469            if (mMaxWidthMode == EMS) {
8470                width = Math.min(width, mMaxWidth * getLineHeight());
8471            } else {
8472                width = Math.min(width, mMaxWidth);
8473            }
8474
8475            if (mMinWidthMode == EMS) {
8476                width = Math.max(width, mMinWidth * getLineHeight());
8477            } else {
8478                width = Math.max(width, mMinWidth);
8479            }
8480
8481            // Check against our minimum width
8482            width = Math.max(width, getSuggestedMinimumWidth());
8483
8484            if (widthMode == MeasureSpec.AT_MOST) {
8485                width = Math.min(widthSize, width);
8486            }
8487        }
8488
8489        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
8490        int unpaddedWidth = want;
8491
8492        if (mHorizontallyScrolling) want = VERY_WIDE;
8493
8494        int hintWant = want;
8495        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
8496
8497        if (mLayout == null) {
8498            makeNewLayout(want, hintWant, boring, hintBoring,
8499                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8500        } else {
8501            final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
8502                    || (mLayout.getEllipsizedWidth()
8503                            != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
8504
8505            final boolean widthChanged = (mHint == null) && (mEllipsize == null)
8506                    && (want > mLayout.getWidth())
8507                    && (mLayout instanceof BoringLayout
8508                            || (fromexisting && des >= 0 && des <= want));
8509
8510            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
8511
8512            if (layoutChanged || maximumChanged) {
8513                if (!maximumChanged && widthChanged) {
8514                    mLayout.increaseWidthTo(want);
8515                } else {
8516                    makeNewLayout(want, hintWant, boring, hintBoring,
8517                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8518                }
8519            } else {
8520                // Nothing has changed
8521            }
8522        }
8523
8524        if (heightMode == MeasureSpec.EXACTLY) {
8525            // Parent has told us how big to be. So be it.
8526            height = heightSize;
8527            mDesiredHeightAtMeasure = -1;
8528        } else {
8529            int desired = getDesiredHeight();
8530
8531            height = desired;
8532            mDesiredHeightAtMeasure = desired;
8533
8534            if (heightMode == MeasureSpec.AT_MOST) {
8535                height = Math.min(desired, heightSize);
8536            }
8537        }
8538
8539        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
8540        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
8541            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
8542        }
8543
8544        /*
8545         * We didn't let makeNewLayout() register to bring the cursor into view,
8546         * so do it here if there is any possibility that it is needed.
8547         */
8548        if (mMovement != null
8549                || mLayout.getWidth() > unpaddedWidth
8550                || mLayout.getHeight() > unpaddedHeight) {
8551            registerForPreDraw();
8552        } else {
8553            scrollTo(0, 0);
8554        }
8555
8556        setMeasuredDimension(width, height);
8557    }
8558
8559    /**
8560     * Automatically computes and sets the text size.
8561     */
8562    private void autoSizeText() {
8563        if (!isAutoSizeEnabled()) {
8564            return;
8565        }
8566
8567        if (mNeedsAutoSizeText) {
8568            if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
8569                return;
8570            }
8571
8572            final int availableWidth = mHorizontallyScrolling
8573                    ? VERY_WIDE
8574                    : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
8575            final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
8576                    - getExtendedPaddingTop();
8577
8578            if (availableWidth <= 0 || availableHeight <= 0) {
8579                return;
8580            }
8581
8582            synchronized (TEMP_RECTF) {
8583                TEMP_RECTF.setEmpty();
8584                TEMP_RECTF.right = availableWidth;
8585                TEMP_RECTF.bottom = availableHeight;
8586                final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
8587
8588                if (optimalTextSize != getTextSize()) {
8589                    setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
8590                            false /* shouldRequestLayout */);
8591
8592                    makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
8593                            mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8594                            false /* bringIntoView */);
8595                }
8596            }
8597        }
8598        // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
8599        // after the next layout pass should set this to false.
8600        mNeedsAutoSizeText = true;
8601    }
8602
8603    /**
8604     * Performs a binary search to find the largest text size that will still fit within the size
8605     * available to this view.
8606     */
8607    private int findLargestTextSizeWhichFits(RectF availableSpace) {
8608        final int sizesCount = mAutoSizeTextSizesInPx.length;
8609        if (sizesCount == 0) {
8610            throw new IllegalStateException("No available text sizes to choose from.");
8611        }
8612
8613        int bestSizeIndex = 0;
8614        int lowIndex = bestSizeIndex + 1;
8615        int highIndex = sizesCount - 1;
8616        int sizeToTryIndex;
8617        while (lowIndex <= highIndex) {
8618            sizeToTryIndex = (lowIndex + highIndex) / 2;
8619            if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
8620                bestSizeIndex = lowIndex;
8621                lowIndex = sizeToTryIndex + 1;
8622            } else {
8623                highIndex = sizeToTryIndex - 1;
8624                bestSizeIndex = highIndex;
8625            }
8626        }
8627
8628        return mAutoSizeTextSizesInPx[bestSizeIndex];
8629    }
8630
8631    private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
8632        final CharSequence text = mTransformed != null
8633                ? mTransformed
8634                : getText();
8635        final int maxLines = getMaxLines();
8636        if (mTempTextPaint == null) {
8637            mTempTextPaint = new TextPaint();
8638        } else {
8639            mTempTextPaint.reset();
8640        }
8641        mTempTextPaint.set(getPaint());
8642        mTempTextPaint.setTextSize(suggestedSizeInPx);
8643
8644        final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
8645                text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
8646
8647        layoutBuilder.setAlignment(getLayoutAlignment())
8648                .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
8649                .setIncludePad(getIncludeFontPadding())
8650                .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
8651                .setBreakStrategy(getBreakStrategy())
8652                .setHyphenationFrequency(getHyphenationFrequency())
8653                .setJustificationMode(getJustificationMode())
8654                .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
8655                .setTextDirection(getTextDirectionHeuristic());
8656
8657        final StaticLayout layout = layoutBuilder.build();
8658
8659        // Lines overflow.
8660        if (maxLines != -1 && layout.getLineCount() > maxLines) {
8661            return false;
8662        }
8663
8664        // Height overflow.
8665        if (layout.getHeight() > availableSpace.bottom) {
8666            return false;
8667        }
8668
8669        return true;
8670    }
8671
8672    private int getDesiredHeight() {
8673        return Math.max(
8674                getDesiredHeight(mLayout, true),
8675                getDesiredHeight(mHintLayout, mEllipsize != null));
8676    }
8677
8678    private int getDesiredHeight(Layout layout, boolean cap) {
8679        if (layout == null) {
8680            return 0;
8681        }
8682
8683        /*
8684        * Don't cap the hint to a certain number of lines.
8685        * (Do cap it, though, if we have a maximum pixel height.)
8686        */
8687        int desired = layout.getHeight(cap);
8688
8689        final Drawables dr = mDrawables;
8690        if (dr != null) {
8691            desired = Math.max(desired, dr.mDrawableHeightLeft);
8692            desired = Math.max(desired, dr.mDrawableHeightRight);
8693        }
8694
8695        int linecount = layout.getLineCount();
8696        final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
8697        desired += padding;
8698
8699        if (mMaxMode != LINES) {
8700            desired = Math.min(desired, mMaximum);
8701        } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
8702                || layout instanceof BoringLayout)) {
8703            desired = layout.getLineTop(mMaximum);
8704
8705            if (dr != null) {
8706                desired = Math.max(desired, dr.mDrawableHeightLeft);
8707                desired = Math.max(desired, dr.mDrawableHeightRight);
8708            }
8709
8710            desired += padding;
8711            linecount = mMaximum;
8712        }
8713
8714        if (mMinMode == LINES) {
8715            if (linecount < mMinimum) {
8716                desired += getLineHeight() * (mMinimum - linecount);
8717            }
8718        } else {
8719            desired = Math.max(desired, mMinimum);
8720        }
8721
8722        // Check against our minimum height
8723        desired = Math.max(desired, getSuggestedMinimumHeight());
8724
8725        return desired;
8726    }
8727
8728    /**
8729     * Check whether a change to the existing text layout requires a
8730     * new view layout.
8731     */
8732    private void checkForResize() {
8733        boolean sizeChanged = false;
8734
8735        if (mLayout != null) {
8736            // Check if our width changed
8737            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
8738                sizeChanged = true;
8739                invalidate();
8740            }
8741
8742            // Check if our height changed
8743            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
8744                int desiredHeight = getDesiredHeight();
8745
8746                if (desiredHeight != this.getHeight()) {
8747                    sizeChanged = true;
8748                }
8749            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
8750                if (mDesiredHeightAtMeasure >= 0) {
8751                    int desiredHeight = getDesiredHeight();
8752
8753                    if (desiredHeight != mDesiredHeightAtMeasure) {
8754                        sizeChanged = true;
8755                    }
8756                }
8757            }
8758        }
8759
8760        if (sizeChanged) {
8761            requestLayout();
8762            // caller will have already invalidated
8763        }
8764    }
8765
8766    /**
8767     * Check whether entirely new text requires a new view layout
8768     * or merely a new text layout.
8769     */
8770    private void checkForRelayout() {
8771        // If we have a fixed width, we can just swap in a new text layout
8772        // if the text height stays the same or if the view height is fixed.
8773
8774        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
8775                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
8776                && (mHint == null || mHintLayout != null)
8777                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
8778            // Static width, so try making a new text layout.
8779
8780            int oldht = mLayout.getHeight();
8781            int want = mLayout.getWidth();
8782            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
8783
8784            /*
8785             * No need to bring the text into view, since the size is not
8786             * changing (unless we do the requestLayout(), in which case it
8787             * will happen at measure).
8788             */
8789            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
8790                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8791                          false);
8792
8793            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
8794                // In a fixed-height view, so use our new text layout.
8795                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
8796                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
8797                    autoSizeText();
8798                    invalidate();
8799                    return;
8800                }
8801
8802                // Dynamic height, but height has stayed the same,
8803                // so use our new text layout.
8804                if (mLayout.getHeight() == oldht
8805                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
8806                    autoSizeText();
8807                    invalidate();
8808                    return;
8809                }
8810            }
8811
8812            // We lose: the height has changed and we have a dynamic height.
8813            // Request a new view layout using our new text layout.
8814            requestLayout();
8815            invalidate();
8816        } else {
8817            // Dynamic width, so we have no choice but to request a new
8818            // view layout with a new text layout.
8819            nullLayouts();
8820            requestLayout();
8821            invalidate();
8822        }
8823    }
8824
8825    @Override
8826    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8827        super.onLayout(changed, left, top, right, bottom);
8828        if (mDeferScroll >= 0) {
8829            int curs = mDeferScroll;
8830            mDeferScroll = -1;
8831            bringPointIntoView(Math.min(curs, mText.length()));
8832        }
8833        // Call auto-size after the width and height have been calculated.
8834        autoSizeText();
8835    }
8836
8837    private boolean isShowingHint() {
8838        return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
8839    }
8840
8841    /**
8842     * Returns true if anything changed.
8843     */
8844    private boolean bringTextIntoView() {
8845        Layout layout = isShowingHint() ? mHintLayout : mLayout;
8846        int line = 0;
8847        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8848            line = layout.getLineCount() - 1;
8849        }
8850
8851        Layout.Alignment a = layout.getParagraphAlignment(line);
8852        int dir = layout.getParagraphDirection(line);
8853        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8854        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8855        int ht = layout.getHeight();
8856
8857        int scrollx, scrolly;
8858
8859        // Convert to left, center, or right alignment.
8860        if (a == Layout.Alignment.ALIGN_NORMAL) {
8861            a = dir == Layout.DIR_LEFT_TO_RIGHT
8862                    ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8863        } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
8864            a = dir == Layout.DIR_LEFT_TO_RIGHT
8865                    ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8866        }
8867
8868        if (a == Layout.Alignment.ALIGN_CENTER) {
8869            /*
8870             * Keep centered if possible, or, if it is too wide to fit,
8871             * keep leading edge in view.
8872             */
8873
8874            int left = (int) Math.floor(layout.getLineLeft(line));
8875            int right = (int) Math.ceil(layout.getLineRight(line));
8876
8877            if (right - left < hspace) {
8878                scrollx = (right + left) / 2 - hspace / 2;
8879            } else {
8880                if (dir < 0) {
8881                    scrollx = right - hspace;
8882                } else {
8883                    scrollx = left;
8884                }
8885            }
8886        } else if (a == Layout.Alignment.ALIGN_RIGHT) {
8887            int right = (int) Math.ceil(layout.getLineRight(line));
8888            scrollx = right - hspace;
8889        } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
8890            scrollx = (int) Math.floor(layout.getLineLeft(line));
8891        }
8892
8893        if (ht < vspace) {
8894            scrolly = 0;
8895        } else {
8896            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8897                scrolly = ht - vspace;
8898            } else {
8899                scrolly = 0;
8900            }
8901        }
8902
8903        if (scrollx != mScrollX || scrolly != mScrollY) {
8904            scrollTo(scrollx, scrolly);
8905            return true;
8906        } else {
8907            return false;
8908        }
8909    }
8910
8911    /**
8912     * Move the point, specified by the offset, into the view if it is needed.
8913     * This has to be called after layout. Returns true if anything changed.
8914     */
8915    public boolean bringPointIntoView(int offset) {
8916        if (isLayoutRequested()) {
8917            mDeferScroll = offset;
8918            return false;
8919        }
8920        boolean changed = false;
8921
8922        Layout layout = isShowingHint() ? mHintLayout : mLayout;
8923
8924        if (layout == null) return changed;
8925
8926        int line = layout.getLineForOffset(offset);
8927
8928        int grav;
8929
8930        switch (layout.getParagraphAlignment(line)) {
8931            case ALIGN_LEFT:
8932                grav = 1;
8933                break;
8934            case ALIGN_RIGHT:
8935                grav = -1;
8936                break;
8937            case ALIGN_NORMAL:
8938                grav = layout.getParagraphDirection(line);
8939                break;
8940            case ALIGN_OPPOSITE:
8941                grav = -layout.getParagraphDirection(line);
8942                break;
8943            case ALIGN_CENTER:
8944            default:
8945                grav = 0;
8946                break;
8947        }
8948
8949        // We only want to clamp the cursor to fit within the layout width
8950        // in left-to-right modes, because in a right to left alignment,
8951        // we want to scroll to keep the line-right on the screen, as other
8952        // lines are likely to have text flush with the right margin, which
8953        // we want to keep visible.
8954        // A better long-term solution would probably be to measure both
8955        // the full line and a blank-trimmed version, and, for example, use
8956        // the latter measurement for centering and right alignment, but for
8957        // the time being we only implement the cursor clamping in left to
8958        // right where it is most likely to be annoying.
8959        final boolean clamped = grav > 0;
8960        // FIXME: Is it okay to truncate this, or should we round?
8961        final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
8962        final int top = layout.getLineTop(line);
8963        final int bottom = layout.getLineTop(line + 1);
8964
8965        int left = (int) Math.floor(layout.getLineLeft(line));
8966        int right = (int) Math.ceil(layout.getLineRight(line));
8967        int ht = layout.getHeight();
8968
8969        int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8970        int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8971        if (!mHorizontallyScrolling && right - left > hspace && right > x) {
8972            // If cursor has been clamped, make sure we don't scroll.
8973            right = Math.max(x, left + hspace);
8974        }
8975
8976        int hslack = (bottom - top) / 2;
8977        int vslack = hslack;
8978
8979        if (vslack > vspace / 4) {
8980            vslack = vspace / 4;
8981        }
8982        if (hslack > hspace / 4) {
8983            hslack = hspace / 4;
8984        }
8985
8986        int hs = mScrollX;
8987        int vs = mScrollY;
8988
8989        if (top - vs < vslack) {
8990            vs = top - vslack;
8991        }
8992        if (bottom - vs > vspace - vslack) {
8993            vs = bottom - (vspace - vslack);
8994        }
8995        if (ht - vs < vspace) {
8996            vs = ht - vspace;
8997        }
8998        if (0 - vs > 0) {
8999            vs = 0;
9000        }
9001
9002        if (grav != 0) {
9003            if (x - hs < hslack) {
9004                hs = x - hslack;
9005            }
9006            if (x - hs > hspace - hslack) {
9007                hs = x - (hspace - hslack);
9008            }
9009        }
9010
9011        if (grav < 0) {
9012            if (left - hs > 0) {
9013                hs = left;
9014            }
9015            if (right - hs < hspace) {
9016                hs = right - hspace;
9017            }
9018        } else if (grav > 0) {
9019            if (right - hs < hspace) {
9020                hs = right - hspace;
9021            }
9022            if (left - hs > 0) {
9023                hs = left;
9024            }
9025        } else /* grav == 0 */ {
9026            if (right - left <= hspace) {
9027                /*
9028                 * If the entire text fits, center it exactly.
9029                 */
9030                hs = left - (hspace - (right - left)) / 2;
9031            } else if (x > right - hslack) {
9032                /*
9033                 * If we are near the right edge, keep the right edge
9034                 * at the edge of the view.
9035                 */
9036                hs = right - hspace;
9037            } else if (x < left + hslack) {
9038                /*
9039                 * If we are near the left edge, keep the left edge
9040                 * at the edge of the view.
9041                 */
9042                hs = left;
9043            } else if (left > hs) {
9044                /*
9045                 * Is there whitespace visible at the left?  Fix it if so.
9046                 */
9047                hs = left;
9048            } else if (right < hs + hspace) {
9049                /*
9050                 * Is there whitespace visible at the right?  Fix it if so.
9051                 */
9052                hs = right - hspace;
9053            } else {
9054                /*
9055                 * Otherwise, float as needed.
9056                 */
9057                if (x - hs < hslack) {
9058                    hs = x - hslack;
9059                }
9060                if (x - hs > hspace - hslack) {
9061                    hs = x - (hspace - hslack);
9062                }
9063            }
9064        }
9065
9066        if (hs != mScrollX || vs != mScrollY) {
9067            if (mScroller == null) {
9068                scrollTo(hs, vs);
9069            } else {
9070                long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
9071                int dx = hs - mScrollX;
9072                int dy = vs - mScrollY;
9073
9074                if (duration > ANIMATED_SCROLL_GAP) {
9075                    mScroller.startScroll(mScrollX, mScrollY, dx, dy);
9076                    awakenScrollBars(mScroller.getDuration());
9077                    invalidate();
9078                } else {
9079                    if (!mScroller.isFinished()) {
9080                        mScroller.abortAnimation();
9081                    }
9082
9083                    scrollBy(dx, dy);
9084                }
9085
9086                mLastScroll = AnimationUtils.currentAnimationTimeMillis();
9087            }
9088
9089            changed = true;
9090        }
9091
9092        if (isFocused()) {
9093            // This offsets because getInterestingRect() is in terms of viewport coordinates, but
9094            // requestRectangleOnScreen() is in terms of content coordinates.
9095
9096            // The offsets here are to ensure the rectangle we are using is
9097            // within our view bounds, in case the cursor is on the far left
9098            // or right.  If it isn't withing the bounds, then this request
9099            // will be ignored.
9100            if (mTempRect == null) mTempRect = new Rect();
9101            mTempRect.set(x - 2, top, x + 2, bottom);
9102            getInterestingRect(mTempRect, line);
9103            mTempRect.offset(mScrollX, mScrollY);
9104
9105            if (requestRectangleOnScreen(mTempRect)) {
9106                changed = true;
9107            }
9108        }
9109
9110        return changed;
9111    }
9112
9113    /**
9114     * Move the cursor, if needed, so that it is at an offset that is visible
9115     * to the user.  This will not move the cursor if it represents more than
9116     * one character (a selection range).  This will only work if the
9117     * TextView contains spannable text; otherwise it will do nothing.
9118     *
9119     * @return True if the cursor was actually moved, false otherwise.
9120     */
9121    public boolean moveCursorToVisibleOffset() {
9122        if (!(mText instanceof Spannable)) {
9123            return false;
9124        }
9125        int start = getSelectionStart();
9126        int end = getSelectionEnd();
9127        if (start != end) {
9128            return false;
9129        }
9130
9131        // First: make sure the line is visible on screen:
9132
9133        int line = mLayout.getLineForOffset(start);
9134
9135        final int top = mLayout.getLineTop(line);
9136        final int bottom = mLayout.getLineTop(line + 1);
9137        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
9138        int vslack = (bottom - top) / 2;
9139        if (vslack > vspace / 4) {
9140            vslack = vspace / 4;
9141        }
9142        final int vs = mScrollY;
9143
9144        if (top < (vs + vslack)) {
9145            line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
9146        } else if (bottom > (vspace + vs - vslack)) {
9147            line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
9148        }
9149
9150        // Next: make sure the character is visible on screen:
9151
9152        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9153        final int hs = mScrollX;
9154        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
9155        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
9156
9157        // line might contain bidirectional text
9158        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
9159        final int highChar = leftChar > rightChar ? leftChar : rightChar;
9160
9161        int newStart = start;
9162        if (newStart < lowChar) {
9163            newStart = lowChar;
9164        } else if (newStart > highChar) {
9165            newStart = highChar;
9166        }
9167
9168        if (newStart != start) {
9169            Selection.setSelection((Spannable) mText, newStart);
9170            return true;
9171        }
9172
9173        return false;
9174    }
9175
9176    @Override
9177    public void computeScroll() {
9178        if (mScroller != null) {
9179            if (mScroller.computeScrollOffset()) {
9180                mScrollX = mScroller.getCurrX();
9181                mScrollY = mScroller.getCurrY();
9182                invalidateParentCaches();
9183                postInvalidate();  // So we draw again
9184            }
9185        }
9186    }
9187
9188    private void getInterestingRect(Rect r, int line) {
9189        convertFromViewportToContentCoordinates(r);
9190
9191        // Rectangle can can be expanded on first and last line to take
9192        // padding into account.
9193        // TODO Take left/right padding into account too?
9194        if (line == 0) r.top -= getExtendedPaddingTop();
9195        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
9196    }
9197
9198    private void convertFromViewportToContentCoordinates(Rect r) {
9199        final int horizontalOffset = viewportToContentHorizontalOffset();
9200        r.left += horizontalOffset;
9201        r.right += horizontalOffset;
9202
9203        final int verticalOffset = viewportToContentVerticalOffset();
9204        r.top += verticalOffset;
9205        r.bottom += verticalOffset;
9206    }
9207
9208    int viewportToContentHorizontalOffset() {
9209        return getCompoundPaddingLeft() - mScrollX;
9210    }
9211
9212    int viewportToContentVerticalOffset() {
9213        int offset = getExtendedPaddingTop() - mScrollY;
9214        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
9215            offset += getVerticalOffset(false);
9216        }
9217        return offset;
9218    }
9219
9220    @Override
9221    public void debug(int depth) {
9222        super.debug(depth);
9223
9224        String output = debugIndent(depth);
9225        output += "frame={" + mLeft + ", " + mTop + ", " + mRight
9226                + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
9227                + "} ";
9228
9229        if (mText != null) {
9230
9231            output += "mText=\"" + mText + "\" ";
9232            if (mLayout != null) {
9233                output += "mLayout width=" + mLayout.getWidth()
9234                        + " height=" + mLayout.getHeight();
9235            }
9236        } else {
9237            output += "mText=NULL";
9238        }
9239        Log.d(VIEW_LOG_TAG, output);
9240    }
9241
9242    /**
9243     * Convenience for {@link Selection#getSelectionStart}.
9244     */
9245    @ViewDebug.ExportedProperty(category = "text")
9246    public int getSelectionStart() {
9247        return Selection.getSelectionStart(getText());
9248    }
9249
9250    /**
9251     * Convenience for {@link Selection#getSelectionEnd}.
9252     */
9253    @ViewDebug.ExportedProperty(category = "text")
9254    public int getSelectionEnd() {
9255        return Selection.getSelectionEnd(getText());
9256    }
9257
9258    /**
9259     * Return true iff there is a selection inside this text view.
9260     */
9261    public boolean hasSelection() {
9262        final int selectionStart = getSelectionStart();
9263        final int selectionEnd = getSelectionEnd();
9264
9265        return selectionStart >= 0 && selectionStart != selectionEnd;
9266    }
9267
9268    String getSelectedText() {
9269        if (!hasSelection()) {
9270            return null;
9271        }
9272
9273        final int start = getSelectionStart();
9274        final int end = getSelectionEnd();
9275        return String.valueOf(
9276                start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
9277    }
9278
9279    /**
9280     * Sets the properties of this field (lines, horizontally scrolling,
9281     * transformation method) to be for a single-line input.
9282     *
9283     * @attr ref android.R.styleable#TextView_singleLine
9284     */
9285    public void setSingleLine() {
9286        setSingleLine(true);
9287    }
9288
9289    /**
9290     * Sets the properties of this field to transform input to ALL CAPS
9291     * display. This may use a "small caps" formatting if available.
9292     * This setting will be ignored if this field is editable or selectable.
9293     *
9294     * This call replaces the current transformation method. Disabling this
9295     * will not necessarily restore the previous behavior from before this
9296     * was enabled.
9297     *
9298     * @see #setTransformationMethod(TransformationMethod)
9299     * @attr ref android.R.styleable#TextView_textAllCaps
9300     */
9301    public void setAllCaps(boolean allCaps) {
9302        if (allCaps) {
9303            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
9304        } else {
9305            setTransformationMethod(null);
9306        }
9307    }
9308
9309    /**
9310     *
9311     * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
9312     * @return Whether the current transformation method is for ALL CAPS.
9313     *
9314     * @see #setAllCaps(boolean)
9315     * @see #setTransformationMethod(TransformationMethod)
9316     */
9317    public boolean isAllCaps() {
9318        final TransformationMethod method = getTransformationMethod();
9319        return method != null && method instanceof AllCapsTransformationMethod;
9320    }
9321
9322    /**
9323     * If true, sets the properties of this field (number of lines, horizontally scrolling,
9324     * transformation method) to be for a single-line input; if false, restores these to the default
9325     * conditions.
9326     *
9327     * Note that the default conditions are not necessarily those that were in effect prior this
9328     * method, and you may want to reset these properties to your custom values.
9329     *
9330     * @attr ref android.R.styleable#TextView_singleLine
9331     */
9332    @android.view.RemotableViewMethod
9333    public void setSingleLine(boolean singleLine) {
9334        // Could be used, but may break backward compatibility.
9335        // if (mSingleLine == singleLine) return;
9336        setInputTypeSingleLine(singleLine);
9337        applySingleLine(singleLine, true, true);
9338    }
9339
9340    /**
9341     * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
9342     * @param singleLine
9343     */
9344    private void setInputTypeSingleLine(boolean singleLine) {
9345        if (mEditor != null
9346                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9347                        == EditorInfo.TYPE_CLASS_TEXT) {
9348            if (singleLine) {
9349                mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9350            } else {
9351                mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9352            }
9353        }
9354    }
9355
9356    private void applySingleLine(boolean singleLine, boolean applyTransformation,
9357            boolean changeMaxLines) {
9358        mSingleLine = singleLine;
9359        if (singleLine) {
9360            setLines(1);
9361            setHorizontallyScrolling(true);
9362            if (applyTransformation) {
9363                setTransformationMethod(SingleLineTransformationMethod.getInstance());
9364            }
9365        } else {
9366            if (changeMaxLines) {
9367                setMaxLines(Integer.MAX_VALUE);
9368            }
9369            setHorizontallyScrolling(false);
9370            if (applyTransformation) {
9371                setTransformationMethod(null);
9372            }
9373        }
9374    }
9375
9376    /**
9377     * Causes words in the text that are longer than the view's width
9378     * to be ellipsized instead of broken in the middle.  You may also
9379     * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
9380     * to constrain the text to a single line.  Use <code>null</code>
9381     * to turn off ellipsizing.
9382     *
9383     * If {@link #setMaxLines} has been used to set two or more lines,
9384     * only {@link android.text.TextUtils.TruncateAt#END} and
9385     * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
9386     * (other ellipsizing types will not do anything).
9387     *
9388     * @attr ref android.R.styleable#TextView_ellipsize
9389     */
9390    public void setEllipsize(TextUtils.TruncateAt where) {
9391        // TruncateAt is an enum. != comparison is ok between these singleton objects.
9392        if (mEllipsize != where) {
9393            mEllipsize = where;
9394
9395            if (mLayout != null) {
9396                nullLayouts();
9397                requestLayout();
9398                invalidate();
9399            }
9400        }
9401    }
9402
9403    /**
9404     * Sets how many times to repeat the marquee animation. Only applied if the
9405     * TextView has marquee enabled. Set to -1 to repeat indefinitely.
9406     *
9407     * @see #getMarqueeRepeatLimit()
9408     *
9409     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9410     */
9411    public void setMarqueeRepeatLimit(int marqueeLimit) {
9412        mMarqueeRepeatLimit = marqueeLimit;
9413    }
9414
9415    /**
9416     * Gets the number of times the marquee animation is repeated. Only meaningful if the
9417     * TextView has marquee enabled.
9418     *
9419     * @return the number of times the marquee animation is repeated. -1 if the animation
9420     * repeats indefinitely
9421     *
9422     * @see #setMarqueeRepeatLimit(int)
9423     *
9424     * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9425     */
9426    public int getMarqueeRepeatLimit() {
9427        return mMarqueeRepeatLimit;
9428    }
9429
9430    /**
9431     * Returns where, if anywhere, words that are longer than the view
9432     * is wide should be ellipsized.
9433     */
9434    @ViewDebug.ExportedProperty
9435    public TextUtils.TruncateAt getEllipsize() {
9436        return mEllipsize;
9437    }
9438
9439    /**
9440     * Set the TextView so that when it takes focus, all the text is
9441     * selected.
9442     *
9443     * @attr ref android.R.styleable#TextView_selectAllOnFocus
9444     */
9445    @android.view.RemotableViewMethod
9446    public void setSelectAllOnFocus(boolean selectAllOnFocus) {
9447        createEditorIfNeeded();
9448        mEditor.mSelectAllOnFocus = selectAllOnFocus;
9449
9450        if (selectAllOnFocus && !(mText instanceof Spannable)) {
9451            setText(mText, BufferType.SPANNABLE);
9452        }
9453    }
9454
9455    /**
9456     * Set whether the cursor is visible. The default is true. Note that this property only
9457     * makes sense for editable TextView.
9458     *
9459     * @see #isCursorVisible()
9460     *
9461     * @attr ref android.R.styleable#TextView_cursorVisible
9462     */
9463    @android.view.RemotableViewMethod
9464    public void setCursorVisible(boolean visible) {
9465        if (visible && mEditor == null) return; // visible is the default value with no edit data
9466        createEditorIfNeeded();
9467        if (mEditor.mCursorVisible != visible) {
9468            mEditor.mCursorVisible = visible;
9469            invalidate();
9470
9471            mEditor.makeBlink();
9472
9473            // InsertionPointCursorController depends on mCursorVisible
9474            mEditor.prepareCursorControllers();
9475        }
9476    }
9477
9478    /**
9479     * @return whether or not the cursor is visible (assuming this TextView is editable)
9480     *
9481     * @see #setCursorVisible(boolean)
9482     *
9483     * @attr ref android.R.styleable#TextView_cursorVisible
9484     */
9485    public boolean isCursorVisible() {
9486        // true is the default value
9487        return mEditor == null ? true : mEditor.mCursorVisible;
9488    }
9489
9490    private boolean canMarquee() {
9491        int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9492        return width > 0 && (mLayout.getLineWidth(0) > width
9493                || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
9494                        && mSavedMarqueeModeLayout.getLineWidth(0) > width));
9495    }
9496
9497    private void startMarquee() {
9498        // Do not ellipsize EditText
9499        if (getKeyListener() != null) return;
9500
9501        if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
9502            return;
9503        }
9504
9505        if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
9506                && getLineCount() == 1 && canMarquee()) {
9507
9508            if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9509                mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
9510                final Layout tmp = mLayout;
9511                mLayout = mSavedMarqueeModeLayout;
9512                mSavedMarqueeModeLayout = tmp;
9513                setHorizontalFadingEdgeEnabled(true);
9514                requestLayout();
9515                invalidate();
9516            }
9517
9518            if (mMarquee == null) mMarquee = new Marquee(this);
9519            mMarquee.start(mMarqueeRepeatLimit);
9520        }
9521    }
9522
9523    private void stopMarquee() {
9524        if (mMarquee != null && !mMarquee.isStopped()) {
9525            mMarquee.stop();
9526        }
9527
9528        if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
9529            mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9530            final Layout tmp = mSavedMarqueeModeLayout;
9531            mSavedMarqueeModeLayout = mLayout;
9532            mLayout = tmp;
9533            setHorizontalFadingEdgeEnabled(false);
9534            requestLayout();
9535            invalidate();
9536        }
9537    }
9538
9539    private void startStopMarquee(boolean start) {
9540        if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9541            if (start) {
9542                startMarquee();
9543            } else {
9544                stopMarquee();
9545            }
9546        }
9547    }
9548
9549    /**
9550     * This method is called when the text is changed, in case any subclasses
9551     * would like to know.
9552     *
9553     * Within <code>text</code>, the <code>lengthAfter</code> characters
9554     * beginning at <code>start</code> have just replaced old text that had
9555     * length <code>lengthBefore</code>. It is an error to attempt to make
9556     * changes to <code>text</code> from this callback.
9557     *
9558     * @param text The text the TextView is displaying
9559     * @param start The offset of the start of the range of the text that was
9560     * modified
9561     * @param lengthBefore The length of the former text that has been replaced
9562     * @param lengthAfter The length of the replacement modified text
9563     */
9564    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
9565        // intentionally empty, template pattern method can be overridden by subclasses
9566    }
9567
9568    /**
9569     * This method is called when the selection has changed, in case any
9570     * subclasses would like to know.
9571     *
9572     * @param selStart The new selection start location.
9573     * @param selEnd The new selection end location.
9574     */
9575    protected void onSelectionChanged(int selStart, int selEnd) {
9576        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
9577    }
9578
9579    /**
9580     * Adds a TextWatcher to the list of those whose methods are called
9581     * whenever this TextView's text changes.
9582     * <p>
9583     * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
9584     * not called after {@link #setText} calls.  Now, doing {@link #setText}
9585     * if there are any text changed listeners forces the buffer type to
9586     * Editable if it would not otherwise be and does call this method.
9587     */
9588    public void addTextChangedListener(TextWatcher watcher) {
9589        if (mListeners == null) {
9590            mListeners = new ArrayList<TextWatcher>();
9591        }
9592
9593        mListeners.add(watcher);
9594    }
9595
9596    /**
9597     * Removes the specified TextWatcher from the list of those whose
9598     * methods are called
9599     * whenever this TextView's text changes.
9600     */
9601    public void removeTextChangedListener(TextWatcher watcher) {
9602        if (mListeners != null) {
9603            int i = mListeners.indexOf(watcher);
9604
9605            if (i >= 0) {
9606                mListeners.remove(i);
9607            }
9608        }
9609    }
9610
9611    private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
9612        if (mListeners != null) {
9613            final ArrayList<TextWatcher> list = mListeners;
9614            final int count = list.size();
9615            for (int i = 0; i < count; i++) {
9616                list.get(i).beforeTextChanged(text, start, before, after);
9617            }
9618        }
9619
9620        // The spans that are inside or intersect the modified region no longer make sense
9621        removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
9622        removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
9623    }
9624
9625    // Removes all spans that are inside or actually overlap the start..end range
9626    private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
9627        if (!(mText instanceof Editable)) return;
9628        Editable text = (Editable) mText;
9629
9630        T[] spans = text.getSpans(start, end, type);
9631        final int length = spans.length;
9632        for (int i = 0; i < length; i++) {
9633            final int spanStart = text.getSpanStart(spans[i]);
9634            final int spanEnd = text.getSpanEnd(spans[i]);
9635            if (spanEnd == start || spanStart == end) break;
9636            text.removeSpan(spans[i]);
9637        }
9638    }
9639
9640    void removeAdjacentSuggestionSpans(final int pos) {
9641        if (!(mText instanceof Editable)) return;
9642        final Editable text = (Editable) mText;
9643
9644        final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
9645        final int length = spans.length;
9646        for (int i = 0; i < length; i++) {
9647            final int spanStart = text.getSpanStart(spans[i]);
9648            final int spanEnd = text.getSpanEnd(spans[i]);
9649            if (spanEnd == pos || spanStart == pos) {
9650                if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
9651                    text.removeSpan(spans[i]);
9652                }
9653            }
9654        }
9655    }
9656
9657    /**
9658     * Not private so it can be called from an inner class without going
9659     * through a thunk.
9660     */
9661    void sendOnTextChanged(CharSequence text, int start, int before, int after) {
9662        if (mListeners != null) {
9663            final ArrayList<TextWatcher> list = mListeners;
9664            final int count = list.size();
9665            for (int i = 0; i < count; i++) {
9666                list.get(i).onTextChanged(text, start, before, after);
9667            }
9668        }
9669
9670        if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
9671    }
9672
9673    /**
9674     * Not private so it can be called from an inner class without going
9675     * through a thunk.
9676     */
9677    void sendAfterTextChanged(Editable text) {
9678        if (mListeners != null) {
9679            final ArrayList<TextWatcher> list = mListeners;
9680            final int count = list.size();
9681            for (int i = 0; i < count; i++) {
9682                list.get(i).afterTextChanged(text);
9683            }
9684        }
9685
9686        // Always notify AutoFillManager - it will return right away if autofill is disabled.
9687        notifyAutoFillManagerAfterTextChangedIfNeeded();
9688
9689        hideErrorIfUnchanged();
9690    }
9691
9692    private void notifyAutoFillManagerAfterTextChangedIfNeeded() {
9693        // It is important to not check whether the view is important for autofill
9694        // since the user can trigger autofill manually on not important views.
9695        if (!isAutofillable()) {
9696            return;
9697        }
9698        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
9699        if (afm != null) {
9700            if (android.view.autofill.Helper.sVerbose) {
9701                Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
9702            }
9703            afm.notifyValueChanged(TextView.this);
9704        }
9705    }
9706
9707    private boolean isAutofillable() {
9708        // It is important to not check whether the view is important for autofill
9709        // since the user can trigger autofill manually on not important views.
9710        return getAutofillType() != AUTOFILL_TYPE_NONE;
9711    }
9712
9713    void updateAfterEdit() {
9714        invalidate();
9715        int curs = getSelectionStart();
9716
9717        if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9718            registerForPreDraw();
9719        }
9720
9721        checkForResize();
9722
9723        if (curs >= 0) {
9724            mHighlightPathBogus = true;
9725            if (mEditor != null) mEditor.makeBlink();
9726            bringPointIntoView(curs);
9727        }
9728    }
9729
9730    /**
9731     * Not private so it can be called from an inner class without going
9732     * through a thunk.
9733     */
9734    void handleTextChanged(CharSequence buffer, int start, int before, int after) {
9735        sLastCutCopyOrTextChangedTime = 0;
9736
9737        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9738        if (ims == null || ims.mBatchEditNesting == 0) {
9739            updateAfterEdit();
9740        }
9741        if (ims != null) {
9742            ims.mContentChanged = true;
9743            if (ims.mChangedStart < 0) {
9744                ims.mChangedStart = start;
9745                ims.mChangedEnd = start + before;
9746            } else {
9747                ims.mChangedStart = Math.min(ims.mChangedStart, start);
9748                ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
9749            }
9750            ims.mChangedDelta += after - before;
9751        }
9752        resetErrorChangedFlag();
9753        sendOnTextChanged(buffer, start, before, after);
9754        onTextChanged(buffer, start, before, after);
9755    }
9756
9757    /**
9758     * Not private so it can be called from an inner class without going
9759     * through a thunk.
9760     */
9761    void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
9762        // XXX Make the start and end move together if this ends up
9763        // spending too much time invalidating.
9764
9765        boolean selChanged = false;
9766        int newSelStart = -1, newSelEnd = -1;
9767
9768        final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9769
9770        if (what == Selection.SELECTION_END) {
9771            selChanged = true;
9772            newSelEnd = newStart;
9773
9774            if (oldStart >= 0 || newStart >= 0) {
9775                invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
9776                checkForResize();
9777                registerForPreDraw();
9778                if (mEditor != null) mEditor.makeBlink();
9779            }
9780        }
9781
9782        if (what == Selection.SELECTION_START) {
9783            selChanged = true;
9784            newSelStart = newStart;
9785
9786            if (oldStart >= 0 || newStart >= 0) {
9787                int end = Selection.getSelectionEnd(buf);
9788                invalidateCursor(end, oldStart, newStart);
9789            }
9790        }
9791
9792        if (selChanged) {
9793            mHighlightPathBogus = true;
9794            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
9795
9796            if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
9797                if (newSelStart < 0) {
9798                    newSelStart = Selection.getSelectionStart(buf);
9799                }
9800                if (newSelEnd < 0) {
9801                    newSelEnd = Selection.getSelectionEnd(buf);
9802                }
9803
9804                if (mEditor != null) {
9805                    mEditor.refreshTextActionMode();
9806                    if (!hasSelection()
9807                            && mEditor.getTextActionMode() == null && hasTransientState()) {
9808                        // User generated selection has been removed.
9809                        setHasTransientState(false);
9810                    }
9811                }
9812                onSelectionChanged(newSelStart, newSelEnd);
9813            }
9814        }
9815
9816        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
9817                || what instanceof CharacterStyle) {
9818            if (ims == null || ims.mBatchEditNesting == 0) {
9819                invalidate();
9820                mHighlightPathBogus = true;
9821                checkForResize();
9822            } else {
9823                ims.mContentChanged = true;
9824            }
9825            if (mEditor != null) {
9826                if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
9827                if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
9828                mEditor.invalidateHandlesAndActionMode();
9829            }
9830        }
9831
9832        if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
9833            mHighlightPathBogus = true;
9834            if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
9835                ims.mSelectionModeChanged = true;
9836            }
9837
9838            if (Selection.getSelectionStart(buf) >= 0) {
9839                if (ims == null || ims.mBatchEditNesting == 0) {
9840                    invalidateCursor();
9841                } else {
9842                    ims.mCursorChanged = true;
9843                }
9844            }
9845        }
9846
9847        if (what instanceof ParcelableSpan) {
9848            // If this is a span that can be sent to a remote process,
9849            // the current extract editor would be interested in it.
9850            if (ims != null && ims.mExtractedTextRequest != null) {
9851                if (ims.mBatchEditNesting != 0) {
9852                    if (oldStart >= 0) {
9853                        if (ims.mChangedStart > oldStart) {
9854                            ims.mChangedStart = oldStart;
9855                        }
9856                        if (ims.mChangedStart > oldEnd) {
9857                            ims.mChangedStart = oldEnd;
9858                        }
9859                    }
9860                    if (newStart >= 0) {
9861                        if (ims.mChangedStart > newStart) {
9862                            ims.mChangedStart = newStart;
9863                        }
9864                        if (ims.mChangedStart > newEnd) {
9865                            ims.mChangedStart = newEnd;
9866                        }
9867                    }
9868                } else {
9869                    if (DEBUG_EXTRACT) {
9870                        Log.v(LOG_TAG, "Span change outside of batch: "
9871                                + oldStart + "-" + oldEnd + ","
9872                                + newStart + "-" + newEnd + " " + what);
9873                    }
9874                    ims.mContentChanged = true;
9875                }
9876            }
9877        }
9878
9879        if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
9880                && what instanceof SpellCheckSpan) {
9881            mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
9882        }
9883    }
9884
9885    @Override
9886    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
9887        if (isTemporarilyDetached()) {
9888            // If we are temporarily in the detach state, then do nothing.
9889            super.onFocusChanged(focused, direction, previouslyFocusedRect);
9890            return;
9891        }
9892
9893        if (mEditor != null) mEditor.onFocusChanged(focused, direction);
9894
9895        if (focused) {
9896            if (mText instanceof Spannable) {
9897                Spannable sp = (Spannable) mText;
9898                MetaKeyKeyListener.resetMetaState(sp);
9899            }
9900        }
9901
9902        startStopMarquee(focused);
9903
9904        if (mTransformation != null) {
9905            mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
9906        }
9907
9908        super.onFocusChanged(focused, direction, previouslyFocusedRect);
9909    }
9910
9911    @Override
9912    public void onWindowFocusChanged(boolean hasWindowFocus) {
9913        super.onWindowFocusChanged(hasWindowFocus);
9914
9915        if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
9916
9917        startStopMarquee(hasWindowFocus);
9918    }
9919
9920    @Override
9921    protected void onVisibilityChanged(View changedView, int visibility) {
9922        super.onVisibilityChanged(changedView, visibility);
9923        if (mEditor != null && visibility != VISIBLE) {
9924            mEditor.hideCursorAndSpanControllers();
9925            stopTextActionMode();
9926        }
9927    }
9928
9929    /**
9930     * Use {@link BaseInputConnection#removeComposingSpans
9931     * BaseInputConnection.removeComposingSpans()} to remove any IME composing
9932     * state from this text view.
9933     */
9934    public void clearComposingText() {
9935        if (mText instanceof Spannable) {
9936            BaseInputConnection.removeComposingSpans((Spannable) mText);
9937        }
9938    }
9939
9940    @Override
9941    public void setSelected(boolean selected) {
9942        boolean wasSelected = isSelected();
9943
9944        super.setSelected(selected);
9945
9946        if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9947            if (selected) {
9948                startMarquee();
9949            } else {
9950                stopMarquee();
9951            }
9952        }
9953    }
9954
9955    @Override
9956    public boolean onTouchEvent(MotionEvent event) {
9957        final int action = event.getActionMasked();
9958        if (mEditor != null) {
9959            mEditor.onTouchEvent(event);
9960
9961            if (mEditor.mSelectionModifierCursorController != null
9962                    && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
9963                return true;
9964            }
9965        }
9966
9967        final boolean superResult = super.onTouchEvent(event);
9968
9969        /*
9970         * Don't handle the release after a long press, because it will move the selection away from
9971         * whatever the menu action was trying to affect. If the long press should have triggered an
9972         * insertion action mode, we can now actually show it.
9973         */
9974        if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
9975            mEditor.mDiscardNextActionUp = false;
9976
9977            if (mEditor.mIsInsertionActionModeStartPending) {
9978                mEditor.startInsertionActionMode();
9979                mEditor.mIsInsertionActionModeStartPending = false;
9980            }
9981            return superResult;
9982        }
9983
9984        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
9985                && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
9986
9987        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
9988                && mText instanceof Spannable && mLayout != null) {
9989            boolean handled = false;
9990
9991            if (mMovement != null) {
9992                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
9993            }
9994
9995            final boolean textIsSelectable = isTextSelectable();
9996            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
9997                // The LinkMovementMethod which should handle taps on links has not been installed
9998                // on non editable text that support text selection.
9999                // We reproduce its behavior here to open links for these.
10000                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
10001                    getSelectionEnd(), ClickableSpan.class);
10002
10003                if (links.length > 0) {
10004                    links[0].onClick(this);
10005                    handled = true;
10006                }
10007            }
10008
10009            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
10010                // Show the IME, except when selecting in read-only text.
10011                final InputMethodManager imm = InputMethodManager.peekInstance();
10012                viewClicked(imm);
10013                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10014                    imm.showSoftInput(this, 0);
10015                }
10016
10017                // The above condition ensures that the mEditor is not null
10018                mEditor.onTouchUpEvent(event);
10019
10020                handled = true;
10021            }
10022
10023            if (handled) {
10024                return true;
10025            }
10026        }
10027
10028        return superResult;
10029    }
10030
10031    @Override
10032    public boolean onGenericMotionEvent(MotionEvent event) {
10033        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10034            try {
10035                if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
10036                    return true;
10037                }
10038            } catch (AbstractMethodError ex) {
10039                // onGenericMotionEvent was added to the MovementMethod interface in API 12.
10040                // Ignore its absence in case third party applications implemented the
10041                // interface directly.
10042            }
10043        }
10044        return super.onGenericMotionEvent(event);
10045    }
10046
10047    @Override
10048    protected void onCreateContextMenu(ContextMenu menu) {
10049        if (mEditor != null) {
10050            mEditor.onCreateContextMenu(menu);
10051        }
10052    }
10053
10054    @Override
10055    public boolean showContextMenu() {
10056        if (mEditor != null) {
10057            mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
10058        }
10059        return super.showContextMenu();
10060    }
10061
10062    @Override
10063    public boolean showContextMenu(float x, float y) {
10064        if (mEditor != null) {
10065            mEditor.setContextMenuAnchor(x, y);
10066        }
10067        return super.showContextMenu(x, y);
10068    }
10069
10070    /**
10071     * @return True iff this TextView contains a text that can be edited, or if this is
10072     * a selectable TextView.
10073     */
10074    boolean isTextEditable() {
10075        return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
10076    }
10077
10078    /**
10079     * Returns true, only while processing a touch gesture, if the initial
10080     * touch down event caused focus to move to the text view and as a result
10081     * its selection changed.  Only valid while processing the touch gesture
10082     * of interest, in an editable text view.
10083     */
10084    public boolean didTouchFocusSelect() {
10085        return mEditor != null && mEditor.mTouchFocusSelected;
10086    }
10087
10088    @Override
10089    public void cancelLongPress() {
10090        super.cancelLongPress();
10091        if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
10092    }
10093
10094    @Override
10095    public boolean onTrackballEvent(MotionEvent event) {
10096        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
10097            if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
10098                return true;
10099            }
10100        }
10101
10102        return super.onTrackballEvent(event);
10103    }
10104
10105    /**
10106     * Sets the Scroller used for producing a scrolling animation
10107     *
10108     * @param s A Scroller instance
10109     */
10110    public void setScroller(Scroller s) {
10111        mScroller = s;
10112    }
10113
10114    @Override
10115    protected float getLeftFadingEdgeStrength() {
10116        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10117            final Marquee marquee = mMarquee;
10118            if (marquee.shouldDrawLeftFade()) {
10119                return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
10120            } else {
10121                return 0.0f;
10122            }
10123        } else if (getLineCount() == 1) {
10124            final float lineLeft = getLayout().getLineLeft(0);
10125            if (lineLeft > mScrollX) return 0.0f;
10126            return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
10127        }
10128        return super.getLeftFadingEdgeStrength();
10129    }
10130
10131    @Override
10132    protected float getRightFadingEdgeStrength() {
10133        if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
10134            final Marquee marquee = mMarquee;
10135            return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
10136        } else if (getLineCount() == 1) {
10137            final float rightEdge = mScrollX +
10138                    (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
10139            final float lineRight = getLayout().getLineRight(0);
10140            if (lineRight < rightEdge) return 0.0f;
10141            return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
10142        }
10143        return super.getRightFadingEdgeStrength();
10144    }
10145
10146    /**
10147     * Calculates the fading edge strength as the ratio of the distance between two
10148     * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
10149     * value for the distance calculation.
10150     *
10151     * @param position1 A horizontal position.
10152     * @param position2 A horizontal position.
10153     * @return Fading edge strength between [0.0f, 1.0f].
10154     */
10155    @FloatRange(from = 0.0, to = 1.0)
10156    private float getHorizontalFadingEdgeStrength(float position1, float position2) {
10157        final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
10158        if (horizontalFadingEdgeLength == 0) return 0.0f;
10159        final float diff = Math.abs(position1 - position2);
10160        if (diff > horizontalFadingEdgeLength) return 1.0f;
10161        return diff / horizontalFadingEdgeLength;
10162    }
10163
10164    private boolean isMarqueeFadeEnabled() {
10165        return mEllipsize == TextUtils.TruncateAt.MARQUEE
10166                && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
10167    }
10168
10169    @Override
10170    protected int computeHorizontalScrollRange() {
10171        if (mLayout != null) {
10172            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
10173                    ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
10174        }
10175
10176        return super.computeHorizontalScrollRange();
10177    }
10178
10179    @Override
10180    protected int computeVerticalScrollRange() {
10181        if (mLayout != null) {
10182            return mLayout.getHeight();
10183        }
10184        return super.computeVerticalScrollRange();
10185    }
10186
10187    @Override
10188    protected int computeVerticalScrollExtent() {
10189        return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
10190    }
10191
10192    @Override
10193    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
10194        super.findViewsWithText(outViews, searched, flags);
10195        if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
10196                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
10197            String searchedLowerCase = searched.toString().toLowerCase();
10198            String textLowerCase = mText.toString().toLowerCase();
10199            if (textLowerCase.contains(searchedLowerCase)) {
10200                outViews.add(this);
10201            }
10202        }
10203    }
10204
10205    /**
10206     * Type of the text buffer that defines the characteristics of the text such as static,
10207     * styleable, or editable.
10208     */
10209    public enum BufferType {
10210        NORMAL, SPANNABLE, EDITABLE
10211    }
10212
10213    /**
10214     * Returns the TextView_textColor attribute from the TypedArray, if set, or
10215     * the TextAppearance_textColor from the TextView_textAppearance attribute,
10216     * if TextView_textColor was not set directly.
10217     *
10218     * @removed
10219     */
10220    public static ColorStateList getTextColors(Context context, TypedArray attrs) {
10221        if (attrs == null) {
10222            // Preserve behavior prior to removal of this API.
10223            throw new NullPointerException();
10224        }
10225
10226        // It's not safe to use this method from apps. The parameter 'attrs'
10227        // must have been obtained using the TextView filter array which is not
10228        // available to the SDK. As such, we grab a default TypedArray with the
10229        // right filter instead here.
10230        final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
10231        ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
10232        if (colors == null) {
10233            final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
10234            if (ap != 0) {
10235                final TypedArray appearance = context.obtainStyledAttributes(
10236                        ap, R.styleable.TextAppearance);
10237                colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
10238                appearance.recycle();
10239            }
10240        }
10241        a.recycle();
10242
10243        return colors;
10244    }
10245
10246    /**
10247     * Returns the default color from the TextView_textColor attribute from the
10248     * AttributeSet, if set, or the default color from the
10249     * TextAppearance_textColor from the TextView_textAppearance attribute, if
10250     * TextView_textColor was not set directly.
10251     *
10252     * @removed
10253     */
10254    public static int getTextColor(Context context, TypedArray attrs, int def) {
10255        final ColorStateList colors = getTextColors(context, attrs);
10256        if (colors == null) {
10257            return def;
10258        } else {
10259            return colors.getDefaultColor();
10260        }
10261    }
10262
10263    @Override
10264    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
10265        if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
10266            // Handle Ctrl-only shortcuts.
10267            switch (keyCode) {
10268                case KeyEvent.KEYCODE_A:
10269                    if (canSelectText()) {
10270                        return onTextContextMenuItem(ID_SELECT_ALL);
10271                    }
10272                    break;
10273                case KeyEvent.KEYCODE_Z:
10274                    if (canUndo()) {
10275                        return onTextContextMenuItem(ID_UNDO);
10276                    }
10277                    break;
10278                case KeyEvent.KEYCODE_X:
10279                    if (canCut()) {
10280                        return onTextContextMenuItem(ID_CUT);
10281                    }
10282                    break;
10283                case KeyEvent.KEYCODE_C:
10284                    if (canCopy()) {
10285                        return onTextContextMenuItem(ID_COPY);
10286                    }
10287                    break;
10288                case KeyEvent.KEYCODE_V:
10289                    if (canPaste()) {
10290                        return onTextContextMenuItem(ID_PASTE);
10291                    }
10292                    break;
10293            }
10294        } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
10295            // Handle Ctrl-Shift shortcuts.
10296            switch (keyCode) {
10297                case KeyEvent.KEYCODE_Z:
10298                    if (canRedo()) {
10299                        return onTextContextMenuItem(ID_REDO);
10300                    }
10301                    break;
10302                case KeyEvent.KEYCODE_V:
10303                    if (canPaste()) {
10304                        return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
10305                    }
10306            }
10307        }
10308        return super.onKeyShortcut(keyCode, event);
10309    }
10310
10311    /**
10312     * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
10313     * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
10314     * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
10315     * sufficient.
10316     */
10317    boolean canSelectText() {
10318        return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
10319    }
10320
10321    /**
10322     * Test based on the <i>intrinsic</i> charateristics of the TextView.
10323     * The text must be spannable and the movement method must allow for arbitary selection.
10324     *
10325     * See also {@link #canSelectText()}.
10326     */
10327    boolean textCanBeSelected() {
10328        // prepareCursorController() relies on this method.
10329        // If you change this condition, make sure prepareCursorController is called anywhere
10330        // the value of this condition might be changed.
10331        if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
10332        return isTextEditable()
10333                || (isTextSelectable() && mText instanceof Spannable && isEnabled());
10334    }
10335
10336    private Locale getTextServicesLocale(boolean allowNullLocale) {
10337        // Start fetching the text services locale asynchronously.
10338        updateTextServicesLocaleAsync();
10339        // If !allowNullLocale and there is no cached text services locale, just return the default
10340        // locale.
10341        return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
10342                : mCurrentSpellCheckerLocaleCache;
10343    }
10344
10345    /**
10346     * This is a temporary method. Future versions may support multi-locale text.
10347     * Caveat: This method may not return the latest text services locale, but this should be
10348     * acceptable and it's more important to make this method asynchronous.
10349     *
10350     * @return The locale that should be used for a word iterator
10351     * in this TextView, based on the current spell checker settings,
10352     * the current IME's locale, or the system default locale.
10353     * Please note that a word iterator in this TextView is different from another word iterator
10354     * used by SpellChecker.java of TextView. This method should be used for the former.
10355     * @hide
10356     */
10357    // TODO: Support multi-locale
10358    // TODO: Update the text services locale immediately after the keyboard locale is switched
10359    // by catching intent of keyboard switch event
10360    public Locale getTextServicesLocale() {
10361        return getTextServicesLocale(false /* allowNullLocale */);
10362    }
10363
10364    /**
10365     * @return {@code true} if this TextView is specialized for showing and interacting with the
10366     * extracted text in a full-screen input method.
10367     * @hide
10368     */
10369    public boolean isInExtractedMode() {
10370        return false;
10371    }
10372
10373    /**
10374     * @return {@code true} if this widget supports auto-sizing text and has been configured to
10375     * auto-size.
10376     */
10377    private boolean isAutoSizeEnabled() {
10378        return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
10379    }
10380
10381    /**
10382     * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
10383     * @hide
10384     */
10385    protected boolean supportsAutoSizeText() {
10386        return true;
10387    }
10388
10389    /**
10390     * This is a temporary method. Future versions may support multi-locale text.
10391     * Caveat: This method may not return the latest spell checker locale, but this should be
10392     * acceptable and it's more important to make this method asynchronous.
10393     *
10394     * @return The locale that should be used for a spell checker in this TextView,
10395     * based on the current spell checker settings, the current IME's locale, or the system default
10396     * locale.
10397     * @hide
10398     */
10399    public Locale getSpellCheckerLocale() {
10400        return getTextServicesLocale(true /* allowNullLocale */);
10401    }
10402
10403    private void updateTextServicesLocaleAsync() {
10404        // AsyncTask.execute() uses a serial executor which means we don't have
10405        // to lock around updateTextServicesLocaleLocked() to prevent it from
10406        // being executed n times in parallel.
10407        AsyncTask.execute(new Runnable() {
10408            @Override
10409            public void run() {
10410                updateTextServicesLocaleLocked();
10411            }
10412        });
10413    }
10414
10415    private void updateTextServicesLocaleLocked() {
10416        final TextServicesManager textServicesManager = (TextServicesManager)
10417                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
10418        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
10419        final Locale locale;
10420        if (subtype != null) {
10421            locale = subtype.getLocaleObject();
10422        } else {
10423            locale = null;
10424        }
10425        mCurrentSpellCheckerLocaleCache = locale;
10426    }
10427
10428    void onLocaleChanged() {
10429        mEditor.onLocaleChanged();
10430    }
10431
10432    /**
10433     * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
10434     * Made available to achieve a consistent behavior.
10435     * @hide
10436     */
10437    public WordIterator getWordIterator() {
10438        if (mEditor != null) {
10439            return mEditor.getWordIterator();
10440        } else {
10441            return null;
10442        }
10443    }
10444
10445    /** @hide */
10446    @Override
10447    public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
10448        super.onPopulateAccessibilityEventInternal(event);
10449
10450        final CharSequence text = getTextForAccessibility();
10451        if (!TextUtils.isEmpty(text)) {
10452            event.getText().add(text);
10453        }
10454    }
10455
10456    @Override
10457    public CharSequence getAccessibilityClassName() {
10458        return TextView.class.getName();
10459    }
10460
10461    @Override
10462    public void onProvideStructure(ViewStructure structure) {
10463        super.onProvideStructure(structure);
10464        onProvideAutoStructureForAssistOrAutofill(structure, false);
10465    }
10466
10467    @Override
10468    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
10469        super.onProvideAutofillStructure(structure, flags);
10470        onProvideAutoStructureForAssistOrAutofill(structure, true);
10471    }
10472
10473    private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure,
10474            boolean forAutofill) {
10475        final boolean isPassword = hasPasswordTransformationMethod()
10476                || isPasswordInputType(getInputType());
10477        if (forAutofill) {
10478            structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
10479            if (mTextId != ResourceId.ID_NULL) {
10480                try {
10481                    structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
10482                } catch (Resources.NotFoundException e) {
10483                    if (android.view.autofill.Helper.sVerbose) {
10484                        Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
10485                                + mTextId + ": " + e.getMessage());
10486                    }
10487                }
10488            }
10489        }
10490
10491        if (!isPassword || forAutofill) {
10492            if (mLayout == null) {
10493                assumeLayout();
10494            }
10495            Layout layout = mLayout;
10496            final int lineCount = layout.getLineCount();
10497            if (lineCount <= 1) {
10498                // Simple case: this is a single line.
10499                final CharSequence text = getText();
10500                if (forAutofill) {
10501                    structure.setText(text);
10502                } else {
10503                    structure.setText(text, getSelectionStart(), getSelectionEnd());
10504                }
10505            } else {
10506                // Complex case: multi-line, could be scrolled or within a scroll container
10507                // so some lines are not visible.
10508                final int[] tmpCords = new int[2];
10509                getLocationInWindow(tmpCords);
10510                final int topWindowLocation = tmpCords[1];
10511                View root = this;
10512                ViewParent viewParent = getParent();
10513                while (viewParent instanceof View) {
10514                    root = (View) viewParent;
10515                    viewParent = root.getParent();
10516                }
10517                final int windowHeight = root.getHeight();
10518                final int topLine;
10519                final int bottomLine;
10520                if (topWindowLocation >= 0) {
10521                    // The top of the view is fully within its window; start text at line 0.
10522                    topLine = getLineAtCoordinateUnclamped(0);
10523                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
10524                } else {
10525                    // The top of hte window has scrolled off the top of the window; figure out
10526                    // the starting line for this.
10527                    topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
10528                    bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
10529                }
10530                // We want to return some contextual lines above/below the lines that are
10531                // actually visible.
10532                int expandedTopLine = topLine - (bottomLine - topLine) / 2;
10533                if (expandedTopLine < 0) {
10534                    expandedTopLine = 0;
10535                }
10536                int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
10537                if (expandedBottomLine >= lineCount) {
10538                    expandedBottomLine = lineCount - 1;
10539                }
10540
10541                // Convert lines into character offsets.
10542                int expandedTopChar = layout.getLineStart(expandedTopLine);
10543                int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
10544
10545                // Take into account selection -- if there is a selection, we need to expand
10546                // the text we are returning to include that selection.
10547                final int selStart = getSelectionStart();
10548                final int selEnd = getSelectionEnd();
10549                if (selStart < selEnd) {
10550                    if (selStart < expandedTopChar) {
10551                        expandedTopChar = selStart;
10552                    }
10553                    if (selEnd > expandedBottomChar) {
10554                        expandedBottomChar = selEnd;
10555                    }
10556                }
10557
10558                // Get the text and trim it to the range we are reporting.
10559                CharSequence text = getText();
10560                if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
10561                    text = text.subSequence(expandedTopChar, expandedBottomChar);
10562                }
10563
10564                if (forAutofill) {
10565                    structure.setText(text);
10566                } else {
10567                    structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
10568
10569                    final int[] lineOffsets = new int[bottomLine - topLine + 1];
10570                    final int[] lineBaselines = new int[bottomLine - topLine + 1];
10571                    final int baselineOffset = getBaselineOffset();
10572                    for (int i = topLine; i <= bottomLine; i++) {
10573                        lineOffsets[i - topLine] = layout.getLineStart(i);
10574                        lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
10575                    }
10576                    structure.setTextLines(lineOffsets, lineBaselines);
10577                }
10578            }
10579
10580            if (!forAutofill) {
10581                // Extract style information that applies to the TextView as a whole.
10582                int style = 0;
10583                int typefaceStyle = getTypefaceStyle();
10584                if ((typefaceStyle & Typeface.BOLD) != 0) {
10585                    style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10586                }
10587                if ((typefaceStyle & Typeface.ITALIC) != 0) {
10588                    style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
10589                }
10590
10591                // Global styles can also be set via TextView.setPaintFlags().
10592                int paintFlags = mTextPaint.getFlags();
10593                if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
10594                    style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10595                }
10596                if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
10597                    style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
10598                }
10599                if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
10600                    style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
10601                }
10602
10603                // TextView does not have its own text background color. A background is either part
10604                // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
10605                structure.setTextStyle(getTextSize(), getCurrentTextColor(),
10606                        AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
10607            } else {
10608                structure.setMinTextEms(getMinEms());
10609                structure.setMaxTextEms(getMaxEms());
10610                int maxLength = -1;
10611                for (InputFilter filter: getFilters()) {
10612                    if (filter instanceof InputFilter.LengthFilter) {
10613                        maxLength = ((InputFilter.LengthFilter) filter).getMax();
10614                        break;
10615                    }
10616                }
10617                structure.setMaxTextLength(maxLength);
10618            }
10619        }
10620        structure.setHint(getHint());
10621        structure.setInputType(getInputType());
10622    }
10623
10624    boolean canRequestAutofill() {
10625        if (!isAutofillable()) {
10626            return false;
10627        }
10628        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10629        if (afm != null) {
10630            return afm.isEnabled();
10631        }
10632        return false;
10633    }
10634
10635    private void requestAutofill() {
10636        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10637        if (afm != null) {
10638            afm.requestAutofill(this);
10639        }
10640    }
10641
10642    @Override
10643    public void autofill(AutofillValue value) {
10644        if (!value.isText() || !isTextEditable()) {
10645            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
10646            return;
10647        }
10648
10649        final CharSequence autofilledValue = value.getTextValue();
10650
10651        // First autofill it...
10652        setText(autofilledValue, mBufferType, true, 0);
10653
10654        // ...then move cursor to the end.
10655        final CharSequence text = getText();
10656        if ((text instanceof Spannable)) {
10657            Selection.setSelection((Spannable) text, text.length());
10658        }
10659    }
10660
10661    @Override
10662    public @AutofillType int getAutofillType() {
10663        return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
10664    }
10665
10666    /**
10667     * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
10668     * {@code char}s if longer.
10669     *
10670     * @return current text, {@code null} if the text is not editable
10671     *
10672     * @see View#getAutofillValue()
10673     */
10674    @Override
10675    @Nullable
10676    public AutofillValue getAutofillValue() {
10677        if (isTextEditable()) {
10678            final CharSequence text = TextUtils.trimToParcelableSize(getText());
10679            return AutofillValue.forText(text);
10680        }
10681        return null;
10682    }
10683
10684    /** @hide */
10685    @Override
10686    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
10687        super.onInitializeAccessibilityEventInternal(event);
10688
10689        final boolean isPassword = hasPasswordTransformationMethod();
10690        event.setPassword(isPassword);
10691
10692        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
10693            event.setFromIndex(Selection.getSelectionStart(mText));
10694            event.setToIndex(Selection.getSelectionEnd(mText));
10695            event.setItemCount(mText.length());
10696        }
10697    }
10698
10699    /** @hide */
10700    @Override
10701    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
10702        super.onInitializeAccessibilityNodeInfoInternal(info);
10703
10704        final boolean isPassword = hasPasswordTransformationMethod();
10705        info.setPassword(isPassword);
10706        info.setText(getTextForAccessibility());
10707        info.setHintText(mHint);
10708        info.setShowingHintText(isShowingHint());
10709        info.setHeading(mIsAccessibilityHeading);
10710
10711        if (mBufferType == BufferType.EDITABLE) {
10712            info.setEditable(true);
10713            if (isEnabled()) {
10714                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
10715            }
10716        }
10717
10718        if (mEditor != null) {
10719            info.setInputType(mEditor.mInputType);
10720
10721            if (mEditor.mError != null) {
10722                info.setContentInvalid(true);
10723                info.setError(mEditor.mError);
10724            }
10725        }
10726
10727        if (!TextUtils.isEmpty(mText)) {
10728            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
10729            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
10730            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
10731                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
10732                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
10733                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
10734                    | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
10735            info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
10736            info.setAvailableExtraData(
10737                    Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
10738        }
10739
10740        if (isFocused()) {
10741            if (canCopy()) {
10742                info.addAction(AccessibilityNodeInfo.ACTION_COPY);
10743            }
10744            if (canPaste()) {
10745                info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
10746            }
10747            if (canCut()) {
10748                info.addAction(AccessibilityNodeInfo.ACTION_CUT);
10749            }
10750            if (canShare()) {
10751                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
10752                        ACCESSIBILITY_ACTION_SHARE,
10753                        getResources().getString(com.android.internal.R.string.share)));
10754            }
10755            if (canProcessText()) {  // also implies mEditor is not null.
10756                mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
10757            }
10758        }
10759
10760        // Check for known input filter types.
10761        final int numFilters = mFilters.length;
10762        for (int i = 0; i < numFilters; i++) {
10763            final InputFilter filter = mFilters[i];
10764            if (filter instanceof InputFilter.LengthFilter) {
10765                info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
10766            }
10767        }
10768
10769        if (!isSingleLine()) {
10770            info.setMultiLine(true);
10771        }
10772    }
10773
10774    @Override
10775    public void addExtraDataToAccessibilityNodeInfo(
10776            AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
10777        // The only extra data we support requires arguments.
10778        if (arguments == null) {
10779            return;
10780        }
10781        if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
10782            int positionInfoStartIndex = arguments.getInt(
10783                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
10784            int positionInfoLength = arguments.getInt(
10785                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
10786            if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
10787                    || (positionInfoStartIndex >= mText.length())) {
10788                Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
10789                return;
10790            }
10791            RectF[] boundingRects = new RectF[positionInfoLength];
10792            final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
10793            populateCharacterBounds(builder, positionInfoStartIndex,
10794                    positionInfoStartIndex + positionInfoLength,
10795                    viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
10796            CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
10797            for (int i = 0; i < positionInfoLength; i++) {
10798                int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
10799                if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
10800                    RectF bounds = cursorAnchorInfo
10801                            .getCharacterBounds(positionInfoStartIndex + i);
10802                    if (bounds != null) {
10803                        mapRectFromViewToScreenCoords(bounds, true);
10804                        boundingRects[i] = bounds;
10805                    }
10806                }
10807            }
10808            info.getExtras().putParcelableArray(extraDataKey, boundingRects);
10809        }
10810    }
10811
10812    /**
10813     * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
10814     *
10815     * @param builder The builder to populate
10816     * @param startIndex The starting character index to populate
10817     * @param endIndex The ending character index to populate
10818     * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
10819     * content
10820     * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
10821     * @hide
10822     */
10823    public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
10824            int startIndex, int endIndex, float viewportToContentHorizontalOffset,
10825            float viewportToContentVerticalOffset) {
10826        final int minLine = mLayout.getLineForOffset(startIndex);
10827        final int maxLine = mLayout.getLineForOffset(endIndex - 1);
10828        for (int line = minLine; line <= maxLine; ++line) {
10829            final int lineStart = mLayout.getLineStart(line);
10830            final int lineEnd = mLayout.getLineEnd(line);
10831            final int offsetStart = Math.max(lineStart, startIndex);
10832            final int offsetEnd = Math.min(lineEnd, endIndex);
10833            final boolean ltrLine =
10834                    mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
10835            final float[] widths = new float[offsetEnd - offsetStart];
10836            mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths);
10837            final float top = mLayout.getLineTop(line);
10838            final float bottom = mLayout.getLineBottom(line);
10839            for (int offset = offsetStart; offset < offsetEnd; ++offset) {
10840                final float charWidth = widths[offset - offsetStart];
10841                final boolean isRtl = mLayout.isRtlCharAt(offset);
10842                final float primary = mLayout.getPrimaryHorizontal(offset);
10843                final float secondary = mLayout.getSecondaryHorizontal(offset);
10844                // TODO: This doesn't work perfectly for text with custom styles and
10845                // TAB chars.
10846                final float left;
10847                final float right;
10848                if (ltrLine) {
10849                    if (isRtl) {
10850                        left = secondary - charWidth;
10851                        right = secondary;
10852                    } else {
10853                        left = primary;
10854                        right = primary + charWidth;
10855                    }
10856                } else {
10857                    if (!isRtl) {
10858                        left = secondary;
10859                        right = secondary + charWidth;
10860                    } else {
10861                        left = primary - charWidth;
10862                        right = primary;
10863                    }
10864                }
10865                // TODO: Check top-right and bottom-left as well.
10866                final float localLeft = left + viewportToContentHorizontalOffset;
10867                final float localRight = right + viewportToContentHorizontalOffset;
10868                final float localTop = top + viewportToContentVerticalOffset;
10869                final float localBottom = bottom + viewportToContentVerticalOffset;
10870                final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
10871                final boolean isBottomRightVisible =
10872                        isPositionVisible(localRight, localBottom);
10873                int characterBoundsFlags = 0;
10874                if (isTopLeftVisible || isBottomRightVisible) {
10875                    characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
10876                }
10877                if (!isTopLeftVisible || !isBottomRightVisible) {
10878                    characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
10879                }
10880                if (isRtl) {
10881                    characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
10882                }
10883                // Here offset is the index in Java chars.
10884                builder.addCharacterBounds(offset, localLeft, localTop, localRight,
10885                        localBottom, characterBoundsFlags);
10886            }
10887        }
10888    }
10889
10890    /**
10891     * @hide
10892     */
10893    public boolean isPositionVisible(final float positionX, final float positionY) {
10894        synchronized (TEMP_POSITION) {
10895            final float[] position = TEMP_POSITION;
10896            position[0] = positionX;
10897            position[1] = positionY;
10898            View view = this;
10899
10900            while (view != null) {
10901                if (view != this) {
10902                    // Local scroll is already taken into account in positionX/Y
10903                    position[0] -= view.getScrollX();
10904                    position[1] -= view.getScrollY();
10905                }
10906
10907                if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
10908                        || position[1] > view.getHeight()) {
10909                    return false;
10910                }
10911
10912                if (!view.getMatrix().isIdentity()) {
10913                    view.getMatrix().mapPoints(position);
10914                }
10915
10916                position[0] += view.getLeft();
10917                position[1] += view.getTop();
10918
10919                final ViewParent parent = view.getParent();
10920                if (parent instanceof View) {
10921                    view = (View) parent;
10922                } else {
10923                    // We've reached the ViewRoot, stop iterating
10924                    view = null;
10925                }
10926            }
10927        }
10928
10929        // We've been able to walk up the view hierarchy and the position was never clipped
10930        return true;
10931    }
10932
10933    /**
10934     * Performs an accessibility action after it has been offered to the
10935     * delegate.
10936     *
10937     * @hide
10938     */
10939    @Override
10940    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
10941        if (mEditor != null
10942                && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
10943            return true;
10944        }
10945        switch (action) {
10946            case AccessibilityNodeInfo.ACTION_CLICK: {
10947                return performAccessibilityActionClick(arguments);
10948            }
10949            case AccessibilityNodeInfo.ACTION_COPY: {
10950                if (isFocused() && canCopy()) {
10951                    if (onTextContextMenuItem(ID_COPY)) {
10952                        return true;
10953                    }
10954                }
10955            } return false;
10956            case AccessibilityNodeInfo.ACTION_PASTE: {
10957                if (isFocused() && canPaste()) {
10958                    if (onTextContextMenuItem(ID_PASTE)) {
10959                        return true;
10960                    }
10961                }
10962            } return false;
10963            case AccessibilityNodeInfo.ACTION_CUT: {
10964                if (isFocused() && canCut()) {
10965                    if (onTextContextMenuItem(ID_CUT)) {
10966                        return true;
10967                    }
10968                }
10969            } return false;
10970            case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
10971                ensureIterableTextForAccessibilitySelectable();
10972                CharSequence text = getIterableTextForAccessibility();
10973                if (text == null) {
10974                    return false;
10975                }
10976                final int start = (arguments != null) ? arguments.getInt(
10977                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
10978                final int end = (arguments != null) ? arguments.getInt(
10979                        AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
10980                if ((getSelectionStart() != start || getSelectionEnd() != end)) {
10981                    // No arguments clears the selection.
10982                    if (start == end && end == -1) {
10983                        Selection.removeSelection((Spannable) text);
10984                        return true;
10985                    }
10986                    if (start >= 0 && start <= end && end <= text.length()) {
10987                        Selection.setSelection((Spannable) text, start, end);
10988                        // Make sure selection mode is engaged.
10989                        if (mEditor != null) {
10990                            mEditor.startSelectionActionModeAsync(false);
10991                        }
10992                        return true;
10993                    }
10994                }
10995            } return false;
10996            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
10997            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
10998                ensureIterableTextForAccessibilitySelectable();
10999                return super.performAccessibilityActionInternal(action, arguments);
11000            }
11001            case ACCESSIBILITY_ACTION_SHARE: {
11002                if (isFocused() && canShare()) {
11003                    if (onTextContextMenuItem(ID_SHARE)) {
11004                        return true;
11005                    }
11006                }
11007            } return false;
11008            case AccessibilityNodeInfo.ACTION_SET_TEXT: {
11009                if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
11010                    return false;
11011                }
11012                CharSequence text = (arguments != null) ? arguments.getCharSequence(
11013                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
11014                setText(text);
11015                if (mText != null) {
11016                    int updatedTextLength = mText.length();
11017                    if (updatedTextLength > 0) {
11018                        Selection.setSelection((Spannable) mText, updatedTextLength);
11019                    }
11020                }
11021            } return true;
11022            default: {
11023                return super.performAccessibilityActionInternal(action, arguments);
11024            }
11025        }
11026    }
11027
11028    private boolean performAccessibilityActionClick(Bundle arguments) {
11029        boolean handled = false;
11030
11031        if (!isEnabled()) {
11032            return false;
11033        }
11034
11035        if (isClickable() || isLongClickable()) {
11036            // Simulate View.onTouchEvent for an ACTION_UP event
11037            if (isFocusable() && !isFocused()) {
11038                requestFocus();
11039            }
11040
11041            performClick();
11042            handled = true;
11043        }
11044
11045        // Show the IME, except when selecting in read-only text.
11046        if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
11047                && (isTextEditable() || isTextSelectable()) && isFocused()) {
11048            final InputMethodManager imm = InputMethodManager.peekInstance();
11049            viewClicked(imm);
11050            if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
11051                handled |= imm.showSoftInput(this, 0);
11052            }
11053        }
11054
11055        return handled;
11056    }
11057
11058    private boolean hasSpannableText() {
11059        return mText != null && mText instanceof Spannable;
11060    }
11061
11062    /** @hide */
11063    @Override
11064    public void sendAccessibilityEventInternal(int eventType) {
11065        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
11066            mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
11067        }
11068
11069        super.sendAccessibilityEventInternal(eventType);
11070    }
11071
11072    @Override
11073    public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
11074        // Do not send scroll events since first they are not interesting for
11075        // accessibility and second such events a generated too frequently.
11076        // For details see the implementation of bringTextIntoView().
11077        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
11078            return;
11079        }
11080        super.sendAccessibilityEventUnchecked(event);
11081    }
11082
11083    /**
11084     * Returns the text that should be exposed to accessibility services.
11085     * <p>
11086     * This approximates what is displayed visually. If the user has specified
11087     * that accessibility services should speak passwords, this method will
11088     * bypass any password transformation method and return unobscured text.
11089     *
11090     * @return the text that should be exposed to accessibility services, may
11091     *         be {@code null} if no text is set
11092     */
11093    @Nullable
11094    private CharSequence getTextForAccessibility() {
11095        // If the text is empty, we must be showing the hint text.
11096        if (TextUtils.isEmpty(mText)) {
11097            return mHint;
11098        }
11099
11100        // Otherwise, return whatever text is being displayed.
11101        return TextUtils.trimToParcelableSize(mTransformed);
11102    }
11103
11104    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
11105            int fromIndex, int removedCount, int addedCount) {
11106        AccessibilityEvent event =
11107                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
11108        event.setFromIndex(fromIndex);
11109        event.setRemovedCount(removedCount);
11110        event.setAddedCount(addedCount);
11111        event.setBeforeText(beforeText);
11112        sendAccessibilityEventUnchecked(event);
11113    }
11114
11115    /**
11116     * Returns whether this text view is a current input method target.  The
11117     * default implementation just checks with {@link InputMethodManager}.
11118     * @return True if the TextView is a current input method target; false otherwise.
11119     */
11120    public boolean isInputMethodTarget() {
11121        InputMethodManager imm = InputMethodManager.peekInstance();
11122        return imm != null && imm.isActive(this);
11123    }
11124
11125    static final int ID_SELECT_ALL = android.R.id.selectAll;
11126    static final int ID_UNDO = android.R.id.undo;
11127    static final int ID_REDO = android.R.id.redo;
11128    static final int ID_CUT = android.R.id.cut;
11129    static final int ID_COPY = android.R.id.copy;
11130    static final int ID_PASTE = android.R.id.paste;
11131    static final int ID_SHARE = android.R.id.shareText;
11132    static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
11133    static final int ID_REPLACE = android.R.id.replaceText;
11134    static final int ID_ASSIST = android.R.id.textAssist;
11135    static final int ID_AUTOFILL = android.R.id.autofill;
11136
11137    /**
11138     * Called when a context menu option for the text view is selected.  Currently
11139     * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
11140     * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
11141     *
11142     * @return true if the context menu item action was performed.
11143     */
11144    public boolean onTextContextMenuItem(int id) {
11145        int min = 0;
11146        int max = mText.length();
11147
11148        if (isFocused()) {
11149            final int selStart = getSelectionStart();
11150            final int selEnd = getSelectionEnd();
11151
11152            min = Math.max(0, Math.min(selStart, selEnd));
11153            max = Math.max(0, Math.max(selStart, selEnd));
11154        }
11155
11156        switch (id) {
11157            case ID_SELECT_ALL:
11158                final boolean hadSelection = hasSelection();
11159                selectAllText();
11160                if (mEditor != null && hadSelection) {
11161                    mEditor.invalidateActionModeAsync();
11162                }
11163                return true;
11164
11165            case ID_UNDO:
11166                if (mEditor != null) {
11167                    mEditor.undo();
11168                }
11169                return true;  // Returns true even if nothing was undone.
11170
11171            case ID_REDO:
11172                if (mEditor != null) {
11173                    mEditor.redo();
11174                }
11175                return true;  // Returns true even if nothing was undone.
11176
11177            case ID_PASTE:
11178                paste(min, max, true /* withFormatting */);
11179                return true;
11180
11181            case ID_PASTE_AS_PLAIN_TEXT:
11182                paste(min, max, false /* withFormatting */);
11183                return true;
11184
11185            case ID_CUT:
11186                final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
11187                if (setPrimaryClip(cutData)) {
11188                    deleteText_internal(min, max);
11189                } else {
11190                    Toast.makeText(getContext(),
11191                            com.android.internal.R.string.failed_to_copy_to_clipboard,
11192                            Toast.LENGTH_SHORT).show();
11193                }
11194                return true;
11195
11196            case ID_COPY:
11197                // For link action mode in a non-selectable/non-focusable TextView,
11198                // make sure that we set the appropriate min/max.
11199                final int selStart = getSelectionStart();
11200                final int selEnd = getSelectionEnd();
11201                min = Math.max(0, Math.min(selStart, selEnd));
11202                max = Math.max(0, Math.max(selStart, selEnd));
11203                final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
11204                if (setPrimaryClip(copyData)) {
11205                    stopTextActionMode();
11206                } else {
11207                    Toast.makeText(getContext(),
11208                            com.android.internal.R.string.failed_to_copy_to_clipboard,
11209                            Toast.LENGTH_SHORT).show();
11210                }
11211                return true;
11212
11213            case ID_REPLACE:
11214                if (mEditor != null) {
11215                    mEditor.replace();
11216                }
11217                return true;
11218
11219            case ID_SHARE:
11220                shareSelectedText();
11221                return true;
11222
11223            case ID_AUTOFILL:
11224                requestAutofill();
11225                stopTextActionMode();
11226                return true;
11227        }
11228        return false;
11229    }
11230
11231    CharSequence getTransformedText(int start, int end) {
11232        return removeSuggestionSpans(mTransformed.subSequence(start, end));
11233    }
11234
11235    @Override
11236    public boolean performLongClick() {
11237        boolean handled = false;
11238        boolean performedHapticFeedback = false;
11239
11240        if (mEditor != null) {
11241            mEditor.mIsBeingLongClicked = true;
11242        }
11243
11244        if (super.performLongClick()) {
11245            handled = true;
11246            performedHapticFeedback = true;
11247        }
11248
11249        if (mEditor != null) {
11250            handled |= mEditor.performLongClick(handled);
11251            mEditor.mIsBeingLongClicked = false;
11252        }
11253
11254        if (handled) {
11255            if (!performedHapticFeedback) {
11256              performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
11257            }
11258            if (mEditor != null) mEditor.mDiscardNextActionUp = true;
11259        } else {
11260            MetricsLogger.action(
11261                    mContext,
11262                    MetricsEvent.TEXT_LONGPRESS,
11263                    TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
11264        }
11265
11266        return handled;
11267    }
11268
11269    @Override
11270    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
11271        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
11272        if (mEditor != null) {
11273            mEditor.onScrollChanged();
11274        }
11275    }
11276
11277    /**
11278     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
11279     * by the IME or by the spell checker as the user types. This is done by adding
11280     * {@link SuggestionSpan}s to the text.
11281     *
11282     * When suggestions are enabled (default), this list of suggestions will be displayed when the
11283     * user asks for them on these parts of the text. This value depends on the inputType of this
11284     * TextView.
11285     *
11286     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
11287     *
11288     * In addition, the type variation must be one of
11289     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
11290     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
11291     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
11292     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
11293     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
11294     *
11295     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
11296     *
11297     * @return true if the suggestions popup window is enabled, based on the inputType.
11298     */
11299    public boolean isSuggestionsEnabled() {
11300        if (mEditor == null) return false;
11301        if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
11302            return false;
11303        }
11304        if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
11305
11306        final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
11307        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
11308                || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
11309                || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
11310                || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
11311                || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
11312    }
11313
11314    /**
11315     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
11316     * selection is initiated in this View.
11317     *
11318     * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
11319     * Paste, Replace and Share actions, depending on what this View supports.
11320     *
11321     * <p>A custom implementation can add new entries in the default menu in its
11322     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
11323     * method. The default actions can also be removed from the menu using
11324     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11325     * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
11326     * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
11327     *
11328     * <p>Returning false from
11329     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
11330     * will prevent the action mode from being started.
11331     *
11332     * <p>Action click events should be handled by the custom implementation of
11333     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
11334     * android.view.MenuItem)}.
11335     *
11336     * <p>Note that text selection mode is not started when a TextView receives focus and the
11337     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
11338     * that case, to allow for quick replacement.
11339     */
11340    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
11341        createEditorIfNeeded();
11342        mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
11343    }
11344
11345    /**
11346     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
11347     *
11348     * @return The current custom selection callback.
11349     */
11350    public ActionMode.Callback getCustomSelectionActionModeCallback() {
11351        return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
11352    }
11353
11354    /**
11355     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
11356     * insertion is initiated in this View.
11357     * The standard implementation populates the menu with a subset of Select All,
11358     * Paste and Replace actions, depending on what this View supports.
11359     *
11360     * <p>A custom implementation can add new entries in the default menu in its
11361     * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
11362     * android.view.Menu)} method. The default actions can also be removed from the menu using
11363     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11364     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
11365     *
11366     * <p>Returning false from
11367     * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
11368     * android.view.Menu)} will prevent the action mode from being started.</p>
11369     *
11370     * <p>Action click events should be handled by the custom implementation of
11371     * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
11372     * android.view.MenuItem)}.</p>
11373     *
11374     * <p>Note that text insertion mode is not started when a TextView receives focus and the
11375     * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
11376     */
11377    public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
11378        createEditorIfNeeded();
11379        mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
11380    }
11381
11382    /**
11383     * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
11384     *
11385     * @return The current custom insertion callback.
11386     */
11387    public ActionMode.Callback getCustomInsertionActionModeCallback() {
11388        return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
11389    }
11390
11391    /**
11392     * Sets the {@link TextClassifier} for this TextView.
11393     */
11394    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
11395        mTextClassifier = textClassifier;
11396    }
11397
11398    /**
11399     * Returns the {@link TextClassifier} used by this TextView.
11400     * If no TextClassifier has been set, this TextView uses the default set by the
11401     * {@link TextClassificationManager}.
11402     */
11403    @NonNull
11404    public TextClassifier getTextClassifier() {
11405        if (mTextClassifier == null) {
11406            TextClassificationManager tcm =
11407                    mContext.getSystemService(TextClassificationManager.class);
11408            if (tcm != null) {
11409                mTextClassifier = tcm.getTextClassifier();
11410            } else {
11411                mTextClassifier = TextClassifier.NO_OP;
11412            }
11413        }
11414        return mTextClassifier;
11415    }
11416
11417    /**
11418     * Starts an ActionMode for the specified TextLink.
11419     *
11420     * @return Whether or not we're attempting to start the action mode.
11421     * @hide
11422     */
11423    public boolean requestActionMode(@NonNull TextLinks.TextLink link) {
11424        Preconditions.checkNotNull(link);
11425        createEditorIfNeeded();
11426        mEditor.startLinkActionModeAsync(link);
11427        return true;
11428    }
11429    /**
11430     * @hide
11431     */
11432    protected void stopTextActionMode() {
11433        if (mEditor != null) {
11434            mEditor.stopTextActionMode();
11435        }
11436    }
11437
11438    boolean canUndo() {
11439        return mEditor != null && mEditor.canUndo();
11440    }
11441
11442    boolean canRedo() {
11443        return mEditor != null && mEditor.canRedo();
11444    }
11445
11446    boolean canCut() {
11447        if (hasPasswordTransformationMethod()) {
11448            return false;
11449        }
11450
11451        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
11452                && mEditor.mKeyListener != null) {
11453            return true;
11454        }
11455
11456        return false;
11457    }
11458
11459    boolean canCopy() {
11460        if (hasPasswordTransformationMethod()) {
11461            return false;
11462        }
11463
11464        if (mText.length() > 0 && hasSelection() && mEditor != null) {
11465            return true;
11466        }
11467
11468        return false;
11469    }
11470
11471    boolean canShare() {
11472        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
11473            return false;
11474        }
11475        return canCopy();
11476    }
11477
11478    boolean isDeviceProvisioned() {
11479        if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
11480            mDeviceProvisionedState = Settings.Global.getInt(
11481                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
11482                    ? DEVICE_PROVISIONED_YES
11483                    : DEVICE_PROVISIONED_NO;
11484        }
11485        return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
11486    }
11487
11488    boolean canPaste() {
11489        return (mText instanceof Editable
11490                && mEditor != null && mEditor.mKeyListener != null
11491                && getSelectionStart() >= 0
11492                && getSelectionEnd() >= 0
11493                && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11494                        .hasPrimaryClip());
11495    }
11496
11497    boolean canPasteAsPlainText() {
11498        if (!canPaste()) {
11499            return false;
11500        }
11501
11502        final ClipData clipData =
11503                ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11504                        .getPrimaryClip();
11505        final ClipDescription description = clipData.getDescription();
11506        final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
11507        final CharSequence text = clipData.getItemAt(0).getText();
11508        if (isPlainType && (text instanceof Spanned)) {
11509            Spanned spanned = (Spanned) text;
11510            if (TextUtils.hasStyleSpan(spanned)) {
11511                return true;
11512            }
11513        }
11514        return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
11515    }
11516
11517    boolean canProcessText() {
11518        if (getId() == View.NO_ID) {
11519            return false;
11520        }
11521        return canShare();
11522    }
11523
11524    boolean canSelectAllText() {
11525        return canSelectText() && !hasPasswordTransformationMethod()
11526                && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
11527    }
11528
11529    boolean selectAllText() {
11530        if (mEditor != null) {
11531            // Hide the toolbar before changing the selection to avoid flickering.
11532            mEditor.hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
11533        }
11534        final int length = mText.length();
11535        Selection.setSelection((Spannable) mText, 0, length);
11536        return length > 0;
11537    }
11538
11539    void replaceSelectionWithText(CharSequence text) {
11540        ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
11541    }
11542
11543    /**
11544     * Paste clipboard content between min and max positions.
11545     */
11546    private void paste(int min, int max, boolean withFormatting) {
11547        ClipboardManager clipboard =
11548                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11549        ClipData clip = clipboard.getPrimaryClip();
11550        if (clip != null) {
11551            boolean didFirst = false;
11552            for (int i = 0; i < clip.getItemCount(); i++) {
11553                final CharSequence paste;
11554                if (withFormatting) {
11555                    paste = clip.getItemAt(i).coerceToStyledText(getContext());
11556                } else {
11557                    // Get an item as text and remove all spans by toString().
11558                    final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
11559                    paste = (text instanceof Spanned) ? text.toString() : text;
11560                }
11561                if (paste != null) {
11562                    if (!didFirst) {
11563                        Selection.setSelection((Spannable) mText, max);
11564                        ((Editable) mText).replace(min, max, paste);
11565                        didFirst = true;
11566                    } else {
11567                        ((Editable) mText).insert(getSelectionEnd(), "\n");
11568                        ((Editable) mText).insert(getSelectionEnd(), paste);
11569                    }
11570                }
11571            }
11572            sLastCutCopyOrTextChangedTime = 0;
11573        }
11574    }
11575
11576    private void shareSelectedText() {
11577        String selectedText = getSelectedText();
11578        if (selectedText != null && !selectedText.isEmpty()) {
11579            Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
11580            sharingIntent.setType("text/plain");
11581            sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
11582            selectedText = TextUtils.trimToParcelableSize(selectedText);
11583            sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
11584            getContext().startActivity(Intent.createChooser(sharingIntent, null));
11585            Selection.setSelection((Spannable) mText, getSelectionEnd());
11586        }
11587    }
11588
11589    @CheckResult
11590    private boolean setPrimaryClip(ClipData clip) {
11591        ClipboardManager clipboard =
11592                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11593        try {
11594            clipboard.setPrimaryClip(clip);
11595        } catch (Throwable t) {
11596            return false;
11597        }
11598        sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
11599        return true;
11600    }
11601
11602    /**
11603     * Get the character offset closest to the specified absolute position. A typical use case is to
11604     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
11605     *
11606     * @param x The horizontal absolute position of a point on screen
11607     * @param y The vertical absolute position of a point on screen
11608     * @return the character offset for the character whose position is closest to the specified
11609     *  position. Returns -1 if there is no layout.
11610     */
11611    public int getOffsetForPosition(float x, float y) {
11612        if (getLayout() == null) return -1;
11613        final int line = getLineAtCoordinate(y);
11614        final int offset = getOffsetAtCoordinate(line, x);
11615        return offset;
11616    }
11617
11618    float convertToLocalHorizontalCoordinate(float x) {
11619        x -= getTotalPaddingLeft();
11620        // Clamp the position to inside of the view.
11621        x = Math.max(0.0f, x);
11622        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
11623        x += getScrollX();
11624        return x;
11625    }
11626
11627    int getLineAtCoordinate(float y) {
11628        y -= getTotalPaddingTop();
11629        // Clamp the position to inside of the view.
11630        y = Math.max(0.0f, y);
11631        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
11632        y += getScrollY();
11633        return getLayout().getLineForVertical((int) y);
11634    }
11635
11636    int getLineAtCoordinateUnclamped(float y) {
11637        y -= getTotalPaddingTop();
11638        y += getScrollY();
11639        return getLayout().getLineForVertical((int) y);
11640    }
11641
11642    int getOffsetAtCoordinate(int line, float x) {
11643        x = convertToLocalHorizontalCoordinate(x);
11644        return getLayout().getOffsetForHorizontal(line, x);
11645    }
11646
11647    @Override
11648    public boolean onDragEvent(DragEvent event) {
11649        switch (event.getAction()) {
11650            case DragEvent.ACTION_DRAG_STARTED:
11651                return mEditor != null && mEditor.hasInsertionController();
11652
11653            case DragEvent.ACTION_DRAG_ENTERED:
11654                TextView.this.requestFocus();
11655                return true;
11656
11657            case DragEvent.ACTION_DRAG_LOCATION:
11658                if (mText instanceof Spannable) {
11659                    final int offset = getOffsetForPosition(event.getX(), event.getY());
11660                    Selection.setSelection((Spannable) mText, offset);
11661                }
11662                return true;
11663
11664            case DragEvent.ACTION_DROP:
11665                if (mEditor != null) mEditor.onDrop(event);
11666                return true;
11667
11668            case DragEvent.ACTION_DRAG_ENDED:
11669            case DragEvent.ACTION_DRAG_EXITED:
11670            default:
11671                return true;
11672        }
11673    }
11674
11675    boolean isInBatchEditMode() {
11676        if (mEditor == null) return false;
11677        final Editor.InputMethodState ims = mEditor.mInputMethodState;
11678        if (ims != null) {
11679            return ims.mBatchEditNesting > 0;
11680        }
11681        return mEditor.mInBatchEditControllers;
11682    }
11683
11684    @Override
11685    public void onRtlPropertiesChanged(int layoutDirection) {
11686        super.onRtlPropertiesChanged(layoutDirection);
11687
11688        final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
11689        if (mTextDir != newTextDir) {
11690            mTextDir = newTextDir;
11691            if (mLayout != null) {
11692                checkForRelayout();
11693            }
11694        }
11695    }
11696
11697    /**
11698     * @hide
11699     */
11700    protected TextDirectionHeuristic getTextDirectionHeuristic() {
11701        if (hasPasswordTransformationMethod()) {
11702            // passwords fields should be LTR
11703            return TextDirectionHeuristics.LTR;
11704        }
11705
11706        if (mEditor != null
11707                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
11708                    == EditorInfo.TYPE_CLASS_PHONE) {
11709            // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
11710            // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
11711            // RTL digits.
11712            final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
11713            final String zero = symbols.getDigitStrings()[0];
11714            // In case the zero digit is multi-codepoint, just use the first codepoint to determine
11715            // direction.
11716            final int firstCodepoint = zero.codePointAt(0);
11717            final byte digitDirection = Character.getDirectionality(firstCodepoint);
11718            if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
11719                    || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
11720                return TextDirectionHeuristics.RTL;
11721            } else {
11722                return TextDirectionHeuristics.LTR;
11723            }
11724        }
11725
11726        // Always need to resolve layout direction first
11727        final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
11728
11729        // Now, we can select the heuristic
11730        switch (getTextDirection()) {
11731            default:
11732            case TEXT_DIRECTION_FIRST_STRONG:
11733                return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11734                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
11735            case TEXT_DIRECTION_ANY_RTL:
11736                return TextDirectionHeuristics.ANYRTL_LTR;
11737            case TEXT_DIRECTION_LTR:
11738                return TextDirectionHeuristics.LTR;
11739            case TEXT_DIRECTION_RTL:
11740                return TextDirectionHeuristics.RTL;
11741            case TEXT_DIRECTION_LOCALE:
11742                return TextDirectionHeuristics.LOCALE;
11743            case TEXT_DIRECTION_FIRST_STRONG_LTR:
11744                return TextDirectionHeuristics.FIRSTSTRONG_LTR;
11745            case TEXT_DIRECTION_FIRST_STRONG_RTL:
11746                return TextDirectionHeuristics.FIRSTSTRONG_RTL;
11747        }
11748    }
11749
11750    /**
11751     * @hide
11752     */
11753    @Override
11754    public void onResolveDrawables(int layoutDirection) {
11755        // No need to resolve twice
11756        if (mLastLayoutDirection == layoutDirection) {
11757            return;
11758        }
11759        mLastLayoutDirection = layoutDirection;
11760
11761        // Resolve drawables
11762        if (mDrawables != null) {
11763            if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
11764                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
11765                prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
11766                applyCompoundDrawableTint();
11767            }
11768        }
11769    }
11770
11771    /**
11772     * Prepares a drawable for display by propagating layout direction and
11773     * drawable state.
11774     *
11775     * @param dr the drawable to prepare
11776     */
11777    private void prepareDrawableForDisplay(@Nullable Drawable dr) {
11778        if (dr == null) {
11779            return;
11780        }
11781
11782        dr.setLayoutDirection(getLayoutDirection());
11783
11784        if (dr.isStateful()) {
11785            dr.setState(getDrawableState());
11786            dr.jumpToCurrentState();
11787        }
11788    }
11789
11790    /**
11791     * @hide
11792     */
11793    protected void resetResolvedDrawables() {
11794        super.resetResolvedDrawables();
11795        mLastLayoutDirection = -1;
11796    }
11797
11798    /**
11799     * @hide
11800     */
11801    protected void viewClicked(InputMethodManager imm) {
11802        if (imm != null) {
11803            imm.viewClicked(this);
11804        }
11805    }
11806
11807    /**
11808     * Deletes the range of text [start, end[.
11809     * @hide
11810     */
11811    protected void deleteText_internal(int start, int end) {
11812        ((Editable) mText).delete(start, end);
11813    }
11814
11815    /**
11816     * Replaces the range of text [start, end[ by replacement text
11817     * @hide
11818     */
11819    protected void replaceText_internal(int start, int end, CharSequence text) {
11820        ((Editable) mText).replace(start, end, text);
11821    }
11822
11823    /**
11824     * Sets a span on the specified range of text
11825     * @hide
11826     */
11827    protected void setSpan_internal(Object span, int start, int end, int flags) {
11828        ((Editable) mText).setSpan(span, start, end, flags);
11829    }
11830
11831    /**
11832     * Moves the cursor to the specified offset position in text
11833     * @hide
11834     */
11835    protected void setCursorPosition_internal(int start, int end) {
11836        Selection.setSelection(((Editable) mText), start, end);
11837    }
11838
11839    /**
11840     * An Editor should be created as soon as any of the editable-specific fields (grouped
11841     * inside the Editor object) is assigned to a non-default value.
11842     * This method will create the Editor if needed.
11843     *
11844     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
11845     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
11846     * Editor for backward compatibility, as soon as one of these fields is assigned.
11847     *
11848     * Also note that for performance reasons, the mEditor is created when needed, but not
11849     * reset when no more edit-specific fields are needed.
11850     */
11851    private void createEditorIfNeeded() {
11852        if (mEditor == null) {
11853            mEditor = new Editor(this);
11854        }
11855    }
11856
11857    /**
11858     * @hide
11859     */
11860    @Override
11861    public CharSequence getIterableTextForAccessibility() {
11862        return mText;
11863    }
11864
11865    private void ensureIterableTextForAccessibilitySelectable() {
11866        if (!(mText instanceof Spannable)) {
11867            setText(mText, BufferType.SPANNABLE);
11868        }
11869    }
11870
11871    /**
11872     * @hide
11873     */
11874    @Override
11875    public TextSegmentIterator getIteratorForGranularity(int granularity) {
11876        switch (granularity) {
11877            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
11878                Spannable text = (Spannable) getIterableTextForAccessibility();
11879                if (!TextUtils.isEmpty(text) && getLayout() != null) {
11880                    AccessibilityIterators.LineTextSegmentIterator iterator =
11881                            AccessibilityIterators.LineTextSegmentIterator.getInstance();
11882                    iterator.initialize(text, getLayout());
11883                    return iterator;
11884                }
11885            } break;
11886            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
11887                Spannable text = (Spannable) getIterableTextForAccessibility();
11888                if (!TextUtils.isEmpty(text) && getLayout() != null) {
11889                    AccessibilityIterators.PageTextSegmentIterator iterator =
11890                            AccessibilityIterators.PageTextSegmentIterator.getInstance();
11891                    iterator.initialize(this);
11892                    return iterator;
11893                }
11894            } break;
11895        }
11896        return super.getIteratorForGranularity(granularity);
11897    }
11898
11899    /**
11900     * @hide
11901     */
11902    @Override
11903    public int getAccessibilitySelectionStart() {
11904        return getSelectionStart();
11905    }
11906
11907    /**
11908     * @hide
11909     */
11910    public boolean isAccessibilitySelectionExtendable() {
11911        return true;
11912    }
11913
11914    /**
11915     * @hide
11916     */
11917    @Override
11918    public int getAccessibilitySelectionEnd() {
11919        return getSelectionEnd();
11920    }
11921
11922    /**
11923     * @hide
11924     */
11925    @Override
11926    public void setAccessibilitySelection(int start, int end) {
11927        if (getAccessibilitySelectionStart() == start
11928                && getAccessibilitySelectionEnd() == end) {
11929            return;
11930        }
11931        CharSequence text = getIterableTextForAccessibility();
11932        if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
11933            Selection.setSelection((Spannable) text, start, end);
11934        } else {
11935            Selection.removeSelection((Spannable) text);
11936        }
11937        // Hide all selection controllers used for adjusting selection
11938        // since we are doing so explicitlty by other means and these
11939        // controllers interact with how selection behaves.
11940        if (mEditor != null) {
11941            mEditor.hideCursorAndSpanControllers();
11942            mEditor.stopTextActionMode();
11943        }
11944    }
11945
11946    /** @hide */
11947    @Override
11948    protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
11949        super.encodeProperties(stream);
11950
11951        TruncateAt ellipsize = getEllipsize();
11952        stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
11953        stream.addProperty("text:textSize", getTextSize());
11954        stream.addProperty("text:scaledTextSize", getScaledTextSize());
11955        stream.addProperty("text:typefaceStyle", getTypefaceStyle());
11956        stream.addProperty("text:selectionStart", getSelectionStart());
11957        stream.addProperty("text:selectionEnd", getSelectionEnd());
11958        stream.addProperty("text:curTextColor", mCurTextColor);
11959        stream.addProperty("text:text", mText == null ? null : mText.toString());
11960        stream.addProperty("text:gravity", mGravity);
11961    }
11962
11963    /**
11964     * User interface state that is stored by TextView for implementing
11965     * {@link View#onSaveInstanceState}.
11966     */
11967    public static class SavedState extends BaseSavedState {
11968        int selStart = -1;
11969        int selEnd = -1;
11970        CharSequence text;
11971        boolean frozenWithFocus;
11972        CharSequence error;
11973        ParcelableParcel editorState;  // Optional state from Editor.
11974
11975        SavedState(Parcelable superState) {
11976            super(superState);
11977        }
11978
11979        @Override
11980        public void writeToParcel(Parcel out, int flags) {
11981            super.writeToParcel(out, flags);
11982            out.writeInt(selStart);
11983            out.writeInt(selEnd);
11984            out.writeInt(frozenWithFocus ? 1 : 0);
11985            TextUtils.writeToParcel(text, out, flags);
11986
11987            if (error == null) {
11988                out.writeInt(0);
11989            } else {
11990                out.writeInt(1);
11991                TextUtils.writeToParcel(error, out, flags);
11992            }
11993
11994            if (editorState == null) {
11995                out.writeInt(0);
11996            } else {
11997                out.writeInt(1);
11998                editorState.writeToParcel(out, flags);
11999            }
12000        }
12001
12002        @Override
12003        public String toString() {
12004            String str = "TextView.SavedState{"
12005                    + Integer.toHexString(System.identityHashCode(this))
12006                    + " start=" + selStart + " end=" + selEnd;
12007            if (text != null) {
12008                str += " text=" + text;
12009            }
12010            return str + "}";
12011        }
12012
12013        @SuppressWarnings("hiding")
12014        public static final Parcelable.Creator<SavedState> CREATOR =
12015                new Parcelable.Creator<SavedState>() {
12016                    public SavedState createFromParcel(Parcel in) {
12017                        return new SavedState(in);
12018                    }
12019
12020                    public SavedState[] newArray(int size) {
12021                        return new SavedState[size];
12022                    }
12023                };
12024
12025        private SavedState(Parcel in) {
12026            super(in);
12027            selStart = in.readInt();
12028            selEnd = in.readInt();
12029            frozenWithFocus = (in.readInt() != 0);
12030            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
12031
12032            if (in.readInt() != 0) {
12033                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
12034            }
12035
12036            if (in.readInt() != 0) {
12037                editorState = ParcelableParcel.CREATOR.createFromParcel(in);
12038            }
12039        }
12040    }
12041
12042    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
12043        private char[] mChars;
12044        private int mStart, mLength;
12045
12046        public CharWrapper(char[] chars, int start, int len) {
12047            mChars = chars;
12048            mStart = start;
12049            mLength = len;
12050        }
12051
12052        /* package */ void set(char[] chars, int start, int len) {
12053            mChars = chars;
12054            mStart = start;
12055            mLength = len;
12056        }
12057
12058        public int length() {
12059            return mLength;
12060        }
12061
12062        public char charAt(int off) {
12063            return mChars[off + mStart];
12064        }
12065
12066        @Override
12067        public String toString() {
12068            return new String(mChars, mStart, mLength);
12069        }
12070
12071        public CharSequence subSequence(int start, int end) {
12072            if (start < 0 || end < 0 || start > mLength || end > mLength) {
12073                throw new IndexOutOfBoundsException(start + ", " + end);
12074            }
12075
12076            return new String(mChars, start + mStart, end - start);
12077        }
12078
12079        public void getChars(int start, int end, char[] buf, int off) {
12080            if (start < 0 || end < 0 || start > mLength || end > mLength) {
12081                throw new IndexOutOfBoundsException(start + ", " + end);
12082            }
12083
12084            System.arraycopy(mChars, start + mStart, buf, off, end - start);
12085        }
12086
12087        @Override
12088        public void drawText(BaseCanvas c, int start, int end,
12089                             float x, float y, Paint p) {
12090            c.drawText(mChars, start + mStart, end - start, x, y, p);
12091        }
12092
12093        @Override
12094        public void drawTextRun(BaseCanvas c, int start, int end,
12095                int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
12096            int count = end - start;
12097            int contextCount = contextEnd - contextStart;
12098            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
12099                    contextCount, x, y, isRtl, p);
12100        }
12101
12102        public float measureText(int start, int end, Paint p) {
12103            return p.measureText(mChars, start + mStart, end - start);
12104        }
12105
12106        public int getTextWidths(int start, int end, float[] widths, Paint p) {
12107            return p.getTextWidths(mChars, start + mStart, end - start, widths);
12108        }
12109
12110        public float getTextRunAdvances(int start, int end, int contextStart,
12111                int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
12112                Paint p) {
12113            int count = end - start;
12114            int contextCount = contextEnd - contextStart;
12115            return p.getTextRunAdvances(mChars, start + mStart, count,
12116                    contextStart + mStart, contextCount, isRtl, advances,
12117                    advancesIndex);
12118        }
12119
12120        public int getTextRunCursor(int contextStart, int contextEnd, int dir,
12121                int offset, int cursorOpt, Paint p) {
12122            int contextCount = contextEnd - contextStart;
12123            return p.getTextRunCursor(mChars, contextStart + mStart,
12124                    contextCount, dir, offset + mStart, cursorOpt);
12125        }
12126    }
12127
12128    private static final class Marquee {
12129        // TODO: Add an option to configure this
12130        private static final float MARQUEE_DELTA_MAX = 0.07f;
12131        private static final int MARQUEE_DELAY = 1200;
12132        private static final int MARQUEE_DP_PER_SECOND = 30;
12133
12134        private static final byte MARQUEE_STOPPED = 0x0;
12135        private static final byte MARQUEE_STARTING = 0x1;
12136        private static final byte MARQUEE_RUNNING = 0x2;
12137
12138        private final WeakReference<TextView> mView;
12139        private final Choreographer mChoreographer;
12140
12141        private byte mStatus = MARQUEE_STOPPED;
12142        private final float mPixelsPerMs;
12143        private float mMaxScroll;
12144        private float mMaxFadeScroll;
12145        private float mGhostStart;
12146        private float mGhostOffset;
12147        private float mFadeStop;
12148        private int mRepeatLimit;
12149
12150        private float mScroll;
12151        private long mLastAnimationMs;
12152
12153        Marquee(TextView v) {
12154            final float density = v.getContext().getResources().getDisplayMetrics().density;
12155            mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
12156            mView = new WeakReference<TextView>(v);
12157            mChoreographer = Choreographer.getInstance();
12158        }
12159
12160        private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
12161            @Override
12162            public void doFrame(long frameTimeNanos) {
12163                tick();
12164            }
12165        };
12166
12167        private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
12168            @Override
12169            public void doFrame(long frameTimeNanos) {
12170                mStatus = MARQUEE_RUNNING;
12171                mLastAnimationMs = mChoreographer.getFrameTime();
12172                tick();
12173            }
12174        };
12175
12176        private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
12177            @Override
12178            public void doFrame(long frameTimeNanos) {
12179                if (mStatus == MARQUEE_RUNNING) {
12180                    if (mRepeatLimit >= 0) {
12181                        mRepeatLimit--;
12182                    }
12183                    start(mRepeatLimit);
12184                }
12185            }
12186        };
12187
12188        void tick() {
12189            if (mStatus != MARQUEE_RUNNING) {
12190                return;
12191            }
12192
12193            mChoreographer.removeFrameCallback(mTickCallback);
12194
12195            final TextView textView = mView.get();
12196            if (textView != null && (textView.isFocused() || textView.isSelected())) {
12197                long currentMs = mChoreographer.getFrameTime();
12198                long deltaMs = currentMs - mLastAnimationMs;
12199                mLastAnimationMs = currentMs;
12200                float deltaPx = deltaMs * mPixelsPerMs;
12201                mScroll += deltaPx;
12202                if (mScroll > mMaxScroll) {
12203                    mScroll = mMaxScroll;
12204                    mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
12205                } else {
12206                    mChoreographer.postFrameCallback(mTickCallback);
12207                }
12208                textView.invalidate();
12209            }
12210        }
12211
12212        void stop() {
12213            mStatus = MARQUEE_STOPPED;
12214            mChoreographer.removeFrameCallback(mStartCallback);
12215            mChoreographer.removeFrameCallback(mRestartCallback);
12216            mChoreographer.removeFrameCallback(mTickCallback);
12217            resetScroll();
12218        }
12219
12220        private void resetScroll() {
12221            mScroll = 0.0f;
12222            final TextView textView = mView.get();
12223            if (textView != null) textView.invalidate();
12224        }
12225
12226        void start(int repeatLimit) {
12227            if (repeatLimit == 0) {
12228                stop();
12229                return;
12230            }
12231            mRepeatLimit = repeatLimit;
12232            final TextView textView = mView.get();
12233            if (textView != null && textView.mLayout != null) {
12234                mStatus = MARQUEE_STARTING;
12235                mScroll = 0.0f;
12236                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
12237                        - textView.getCompoundPaddingRight();
12238                final float lineWidth = textView.mLayout.getLineWidth(0);
12239                final float gap = textWidth / 3.0f;
12240                mGhostStart = lineWidth - textWidth + gap;
12241                mMaxScroll = mGhostStart + textWidth;
12242                mGhostOffset = lineWidth + gap;
12243                mFadeStop = lineWidth + textWidth / 6.0f;
12244                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
12245
12246                textView.invalidate();
12247                mChoreographer.postFrameCallback(mStartCallback);
12248            }
12249        }
12250
12251        float getGhostOffset() {
12252            return mGhostOffset;
12253        }
12254
12255        float getScroll() {
12256            return mScroll;
12257        }
12258
12259        float getMaxFadeScroll() {
12260            return mMaxFadeScroll;
12261        }
12262
12263        boolean shouldDrawLeftFade() {
12264            return mScroll <= mFadeStop;
12265        }
12266
12267        boolean shouldDrawGhost() {
12268            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
12269        }
12270
12271        boolean isRunning() {
12272            return mStatus == MARQUEE_RUNNING;
12273        }
12274
12275        boolean isStopped() {
12276            return mStatus == MARQUEE_STOPPED;
12277        }
12278    }
12279
12280    private class ChangeWatcher implements TextWatcher, SpanWatcher {
12281
12282        private CharSequence mBeforeText;
12283
12284        public void beforeTextChanged(CharSequence buffer, int start,
12285                                      int before, int after) {
12286            if (DEBUG_EXTRACT) {
12287                Log.v(LOG_TAG, "beforeTextChanged start=" + start
12288                        + " before=" + before + " after=" + after + ": " + buffer);
12289            }
12290
12291            if (AccessibilityManager.getInstance(mContext).isEnabled()
12292                    && !isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) {
12293                mBeforeText = buffer.toString();
12294            }
12295
12296            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
12297        }
12298
12299        public void onTextChanged(CharSequence buffer, int start, int before, int after) {
12300            if (DEBUG_EXTRACT) {
12301                Log.v(LOG_TAG, "onTextChanged start=" + start
12302                        + " before=" + before + " after=" + after + ": " + buffer);
12303            }
12304            TextView.this.handleTextChanged(buffer, start, before, after);
12305
12306            if (AccessibilityManager.getInstance(mContext).isEnabled()
12307                    && (isFocused() || isSelected() && isShown())) {
12308                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
12309                mBeforeText = null;
12310            }
12311        }
12312
12313        public void afterTextChanged(Editable buffer) {
12314            if (DEBUG_EXTRACT) {
12315                Log.v(LOG_TAG, "afterTextChanged: " + buffer);
12316            }
12317            TextView.this.sendAfterTextChanged(buffer);
12318
12319            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
12320                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
12321            }
12322        }
12323
12324        public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
12325            if (DEBUG_EXTRACT) {
12326                Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
12327                        + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
12328            }
12329            TextView.this.spanChange(buf, what, s, st, e, en);
12330        }
12331
12332        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
12333            if (DEBUG_EXTRACT) {
12334                Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
12335            }
12336            TextView.this.spanChange(buf, what, -1, s, -1, e);
12337        }
12338
12339        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
12340            if (DEBUG_EXTRACT) {
12341                Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
12342            }
12343            TextView.this.spanChange(buf, what, s, -1, e, -1);
12344        }
12345    }
12346}
12347