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