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