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