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