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