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