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