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