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