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 com.android.internal.util.FastMath;
     20 import com.android.internal.widget.EditableInputConnection;
     21 
     22 import org.xmlpull.v1.XmlPullParserException;
     23 
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.PackageManager;
     27 import android.content.res.ColorStateList;
     28 import android.content.res.Resources;
     29 import android.content.res.TypedArray;
     30 import android.content.res.XmlResourceParser;
     31 import android.graphics.Canvas;
     32 import android.graphics.Paint;
     33 import android.graphics.Path;
     34 import android.graphics.Rect;
     35 import android.graphics.RectF;
     36 import android.graphics.Typeface;
     37 import android.graphics.drawable.Drawable;
     38 import android.inputmethodservice.ExtractEditText;
     39 import android.os.Bundle;
     40 import android.os.Handler;
     41 import android.os.Message;
     42 import android.os.Parcel;
     43 import android.os.Parcelable;
     44 import android.os.ResultReceiver;
     45 import android.os.SystemClock;
     46 import android.text.BoringLayout;
     47 import android.text.ClipboardManager;
     48 import android.text.DynamicLayout;
     49 import android.text.Editable;
     50 import android.text.GetChars;
     51 import android.text.GraphicsOperations;
     52 import android.text.InputFilter;
     53 import android.text.InputType;
     54 import android.text.Layout;
     55 import android.text.ParcelableSpan;
     56 import android.text.Selection;
     57 import android.text.SpanWatcher;
     58 import android.text.Spannable;
     59 import android.text.SpannableString;
     60 import android.text.Spanned;
     61 import android.text.SpannedString;
     62 import android.text.StaticLayout;
     63 import android.text.TextPaint;
     64 import android.text.TextUtils;
     65 import android.text.TextWatcher;
     66 import android.text.method.DateKeyListener;
     67 import android.text.method.DateTimeKeyListener;
     68 import android.text.method.DialerKeyListener;
     69 import android.text.method.DigitsKeyListener;
     70 import android.text.method.KeyListener;
     71 import android.text.method.LinkMovementMethod;
     72 import android.text.method.MetaKeyKeyListener;
     73 import android.text.method.MovementMethod;
     74 import android.text.method.PasswordTransformationMethod;
     75 import android.text.method.SingleLineTransformationMethod;
     76 import android.text.method.TextKeyListener;
     77 import android.text.method.TimeKeyListener;
     78 import android.text.method.TransformationMethod;
     79 import android.text.style.ParagraphStyle;
     80 import android.text.style.URLSpan;
     81 import android.text.style.UpdateAppearance;
     82 import android.text.util.Linkify;
     83 import android.util.AttributeSet;
     84 import android.util.FloatMath;
     85 import android.util.Log;
     86 import android.util.TypedValue;
     87 import android.view.ContextMenu;
     88 import android.view.Gravity;
     89 import android.view.KeyEvent;
     90 import android.view.LayoutInflater;
     91 import android.view.MenuItem;
     92 import android.view.MotionEvent;
     93 import android.view.View;
     94 import android.view.ViewConfiguration;
     95 import android.view.ViewDebug;
     96 import android.view.ViewGroup;
     97 import android.view.ViewGroup.LayoutParams;
     98 import android.view.ViewParent;
     99 import android.view.ViewRoot;
    100 import android.view.ViewTreeObserver;
    101 import android.view.WindowManager;
    102 import android.view.accessibility.AccessibilityEvent;
    103 import android.view.accessibility.AccessibilityManager;
    104 import android.view.animation.AnimationUtils;
    105 import android.view.inputmethod.BaseInputConnection;
    106 import android.view.inputmethod.CompletionInfo;
    107 import android.view.inputmethod.EditorInfo;
    108 import android.view.inputmethod.ExtractedText;
    109 import android.view.inputmethod.ExtractedTextRequest;
    110 import android.view.inputmethod.InputConnection;
    111 import android.view.inputmethod.InputMethodManager;
    112 import android.widget.RemoteViews.RemoteView;
    113 
    114 import java.io.IOException;
    115 import java.lang.ref.WeakReference;
    116 import java.util.ArrayList;
    117 
    118 /**
    119  * Displays text to the user and optionally allows them to edit it.  A TextView
    120  * is a complete text editor, however the basic class is configured to not
    121  * allow editing; see {@link EditText} for a subclass that configures the text
    122  * view for editing.
    123  *
    124  * <p>
    125  * <b>XML attributes</b>
    126  * <p>
    127  * See {@link android.R.styleable#TextView TextView Attributes},
    128  * {@link android.R.styleable#View View Attributes}
    129  *
    130  * @attr ref android.R.styleable#TextView_text
    131  * @attr ref android.R.styleable#TextView_bufferType
    132  * @attr ref android.R.styleable#TextView_hint
    133  * @attr ref android.R.styleable#TextView_textColor
    134  * @attr ref android.R.styleable#TextView_textColorHighlight
    135  * @attr ref android.R.styleable#TextView_textColorHint
    136  * @attr ref android.R.styleable#TextView_textAppearance
    137  * @attr ref android.R.styleable#TextView_textColorLink
    138  * @attr ref android.R.styleable#TextView_textSize
    139  * @attr ref android.R.styleable#TextView_textScaleX
    140  * @attr ref android.R.styleable#TextView_typeface
    141  * @attr ref android.R.styleable#TextView_textStyle
    142  * @attr ref android.R.styleable#TextView_cursorVisible
    143  * @attr ref android.R.styleable#TextView_maxLines
    144  * @attr ref android.R.styleable#TextView_maxHeight
    145  * @attr ref android.R.styleable#TextView_lines
    146  * @attr ref android.R.styleable#TextView_height
    147  * @attr ref android.R.styleable#TextView_minLines
    148  * @attr ref android.R.styleable#TextView_minHeight
    149  * @attr ref android.R.styleable#TextView_maxEms
    150  * @attr ref android.R.styleable#TextView_maxWidth
    151  * @attr ref android.R.styleable#TextView_ems
    152  * @attr ref android.R.styleable#TextView_width
    153  * @attr ref android.R.styleable#TextView_minEms
    154  * @attr ref android.R.styleable#TextView_minWidth
    155  * @attr ref android.R.styleable#TextView_gravity
    156  * @attr ref android.R.styleable#TextView_scrollHorizontally
    157  * @attr ref android.R.styleable#TextView_password
    158  * @attr ref android.R.styleable#TextView_singleLine
    159  * @attr ref android.R.styleable#TextView_selectAllOnFocus
    160  * @attr ref android.R.styleable#TextView_includeFontPadding
    161  * @attr ref android.R.styleable#TextView_maxLength
    162  * @attr ref android.R.styleable#TextView_shadowColor
    163  * @attr ref android.R.styleable#TextView_shadowDx
    164  * @attr ref android.R.styleable#TextView_shadowDy
    165  * @attr ref android.R.styleable#TextView_shadowRadius
    166  * @attr ref android.R.styleable#TextView_autoLink
    167  * @attr ref android.R.styleable#TextView_linksClickable
    168  * @attr ref android.R.styleable#TextView_numeric
    169  * @attr ref android.R.styleable#TextView_digits
    170  * @attr ref android.R.styleable#TextView_phoneNumber
    171  * @attr ref android.R.styleable#TextView_inputMethod
    172  * @attr ref android.R.styleable#TextView_capitalize
    173  * @attr ref android.R.styleable#TextView_autoText
    174  * @attr ref android.R.styleable#TextView_editable
    175  * @attr ref android.R.styleable#TextView_freezesText
    176  * @attr ref android.R.styleable#TextView_ellipsize
    177  * @attr ref android.R.styleable#TextView_drawableTop
    178  * @attr ref android.R.styleable#TextView_drawableBottom
    179  * @attr ref android.R.styleable#TextView_drawableRight
    180  * @attr ref android.R.styleable#TextView_drawableLeft
    181  * @attr ref android.R.styleable#TextView_drawablePadding
    182  * @attr ref android.R.styleable#TextView_lineSpacingExtra
    183  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
    184  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
    185  * @attr ref android.R.styleable#TextView_inputType
    186  * @attr ref android.R.styleable#TextView_imeOptions
    187  * @attr ref android.R.styleable#TextView_privateImeOptions
    188  * @attr ref android.R.styleable#TextView_imeActionLabel
    189  * @attr ref android.R.styleable#TextView_imeActionId
    190  * @attr ref android.R.styleable#TextView_editorExtras
    191  */
    192 @RemoteView
    193 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
    194     static final String LOG_TAG = "TextView";
    195     static final boolean DEBUG_EXTRACT = false;
    196 
    197     private static int PRIORITY = 100;
    198 
    199     final int[] mTempCoords = new int[2];
    200     Rect mTempRect;
    201 
    202     private ColorStateList mTextColor;
    203     private int mCurTextColor;
    204     private ColorStateList mHintTextColor;
    205     private ColorStateList mLinkTextColor;
    206     private int mCurHintTextColor;
    207     private boolean mFreezesText;
    208     private boolean mFrozenWithFocus;
    209     private boolean mTemporaryDetach;
    210     private boolean mDispatchTemporaryDetach;
    211 
    212     private boolean mEatTouchRelease = false;
    213     private boolean mScrolled = false;
    214 
    215     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
    216     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
    217 
    218     private float mShadowRadius, mShadowDx, mShadowDy;
    219 
    220     private static final int PREDRAW_NOT_REGISTERED = 0;
    221     private static final int PREDRAW_PENDING = 1;
    222     private static final int PREDRAW_DONE = 2;
    223     private int mPreDrawState = PREDRAW_NOT_REGISTERED;
    224 
    225     private TextUtils.TruncateAt mEllipsize = null;
    226 
    227     // Enum for the "typeface" XML parameter.
    228     // TODO: How can we get this from the XML instead of hardcoding it here?
    229     private static final int SANS = 1;
    230     private static final int SERIF = 2;
    231     private static final int MONOSPACE = 3;
    232 
    233     // Bitfield for the "numeric" XML parameter.
    234     // TODO: How can we get this from the XML instead of hardcoding it here?
    235     private static final int SIGNED = 2;
    236     private static final int DECIMAL = 4;
    237 
    238     class Drawables {
    239         final Rect mCompoundRect = new Rect();
    240         Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
    241         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
    242         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
    243         int mDrawablePadding;
    244     }
    245     private Drawables mDrawables;
    246 
    247     private CharSequence mError;
    248     private boolean mErrorWasChanged;
    249     private ErrorPopup mPopup;
    250     /**
    251      * This flag is set if the TextView tries to display an error before it
    252      * is attached to the window (so its position is still unknown).
    253      * It causes the error to be shown later, when onAttachedToWindow()
    254      * is called.
    255      */
    256     private boolean mShowErrorAfterAttach;
    257 
    258     private CharWrapper mCharWrapper = null;
    259 
    260     private boolean mSelectionMoved = false;
    261     private boolean mTouchFocusSelected = false;
    262 
    263     private Marquee mMarquee;
    264     private boolean mRestartMarquee;
    265 
    266     private int mMarqueeRepeatLimit = 3;
    267 
    268     class InputContentType {
    269         int imeOptions = EditorInfo.IME_NULL;
    270         String privateImeOptions;
    271         CharSequence imeActionLabel;
    272         int imeActionId;
    273         Bundle extras;
    274         OnEditorActionListener onEditorActionListener;
    275         boolean enterDown;
    276     }
    277     InputContentType mInputContentType;
    278 
    279     class InputMethodState {
    280         Rect mCursorRectInWindow = new Rect();
    281         RectF mTmpRectF = new RectF();
    282         float[] mTmpOffset = new float[2];
    283         ExtractedTextRequest mExtracting;
    284         final ExtractedText mTmpExtracted = new ExtractedText();
    285         int mBatchEditNesting;
    286         boolean mCursorChanged;
    287         boolean mSelectionModeChanged;
    288         boolean mContentChanged;
    289         int mChangedStart, mChangedEnd, mChangedDelta;
    290     }
    291     InputMethodState mInputMethodState;
    292 
    293     int mTextSelectHandleLeftRes;
    294     int mTextSelectHandleRightRes;
    295     int mTextSelectHandleRes;
    296 
    297     Drawable mSelectHandleLeft;
    298     Drawable mSelectHandleRight;
    299     Drawable mSelectHandleCenter;
    300 
    301     // Set when this TextView gained focus with some text selected. Will start selection mode.
    302     private boolean mCreatedWithASelection = false;
    303 
    304     private boolean mNoContextMenuOnUp = false;
    305 
    306     /*
    307      * Kick-start the font cache for the zygote process (to pay the cost of
    308      * initializing freetype for our default font only once).
    309      */
    310     static {
    311         Paint p = new Paint();
    312         p.setAntiAlias(true);
    313         // We don't care about the result, just the side-effect of measuring.
    314         p.measureText("H");
    315     }
    316 
    317     /**
    318      * Interface definition for a callback to be invoked when an action is
    319      * performed on the editor.
    320      */
    321     public interface OnEditorActionListener {
    322         /**
    323          * Called when an action is being performed.
    324          *
    325          * @param v The view that was clicked.
    326          * @param actionId Identifier of the action.  This will be either the
    327          * identifier you supplied, or {@link EditorInfo#IME_NULL
    328          * EditorInfo.IME_NULL} if being called due to the enter key
    329          * being pressed.
    330          * @param event If triggered by an enter key, this is the event;
    331          * otherwise, this is null.
    332          * @return Return true if you have consumed the action, else false.
    333          */
    334         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
    335     }
    336 
    337     public TextView(Context context) {
    338         this(context, null);
    339     }
    340 
    341     public TextView(Context context,
    342                     AttributeSet attrs) {
    343         this(context, attrs, com.android.internal.R.attr.textViewStyle);
    344     }
    345 
    346     @SuppressWarnings("deprecation")
    347     public TextView(Context context,
    348                     AttributeSet attrs,
    349                     int defStyle) {
    350         super(context, attrs, defStyle);
    351         mText = "";
    352 
    353         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    354         mTextPaint.density = getResources().getDisplayMetrics().density;
    355         mTextPaint.setCompatibilityScaling(
    356                 getResources().getCompatibilityInfo().applicationScale);
    357 
    358         // If we get the paint from the skin, we should set it to left, since
    359         // the layout always wants it to be left.
    360         // mTextPaint.setTextAlign(Paint.Align.LEFT);
    361 
    362         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    363         mHighlightPaint.setCompatibilityScaling(
    364                 getResources().getCompatibilityInfo().applicationScale);
    365 
    366         mMovement = getDefaultMovementMethod();
    367         mTransformation = null;
    368 
    369         TypedArray a =
    370             context.obtainStyledAttributes(
    371                 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
    372 
    373         int textColorHighlight = 0;
    374         ColorStateList textColor = null;
    375         ColorStateList textColorHint = null;
    376         ColorStateList textColorLink = null;
    377         int textSize = 15;
    378         int typefaceIndex = -1;
    379         int styleIndex = -1;
    380 
    381         /*
    382          * Look the appearance up without checking first if it exists because
    383          * almost every TextView has one and it greatly simplifies the logic
    384          * to be able to parse the appearance first and then let specific tags
    385          * for this View override it.
    386          */
    387         TypedArray appearance = null;
    388         int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
    389         if (ap != -1) {
    390             appearance = context.obtainStyledAttributes(ap,
    391                                 com.android.internal.R.styleable.
    392                                 TextAppearance);
    393         }
    394         if (appearance != null) {
    395             int n = appearance.getIndexCount();
    396             for (int i = 0; i < n; i++) {
    397                 int attr = appearance.getIndex(i);
    398 
    399                 switch (attr) {
    400                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
    401                     textColorHighlight = appearance.getColor(attr, textColorHighlight);
    402                     break;
    403 
    404                 case com.android.internal.R.styleable.TextAppearance_textColor:
    405                     textColor = appearance.getColorStateList(attr);
    406                     break;
    407 
    408                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
    409                     textColorHint = appearance.getColorStateList(attr);
    410                     break;
    411 
    412                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
    413                     textColorLink = appearance.getColorStateList(attr);
    414                     break;
    415 
    416                 case com.android.internal.R.styleable.TextAppearance_textSize:
    417                     textSize = appearance.getDimensionPixelSize(attr, textSize);
    418                     break;
    419 
    420                 case com.android.internal.R.styleable.TextAppearance_typeface:
    421                     typefaceIndex = appearance.getInt(attr, -1);
    422                     break;
    423 
    424                 case com.android.internal.R.styleable.TextAppearance_textStyle:
    425                     styleIndex = appearance.getInt(attr, -1);
    426                     break;
    427                 }
    428             }
    429 
    430             appearance.recycle();
    431         }
    432 
    433         boolean editable = getDefaultEditable();
    434         CharSequence inputMethod = null;
    435         int numeric = 0;
    436         CharSequence digits = null;
    437         boolean phone = false;
    438         boolean autotext = false;
    439         int autocap = -1;
    440         int buffertype = 0;
    441         boolean selectallonfocus = false;
    442         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
    443             drawableBottom = null;
    444         int drawablePadding = 0;
    445         int ellipsize = -1;
    446         boolean singleLine = false;
    447         int maxlength = -1;
    448         CharSequence text = "";
    449         CharSequence hint = null;
    450         int shadowcolor = 0;
    451         float dx = 0, dy = 0, r = 0;
    452         boolean password = false;
    453         int inputType = EditorInfo.TYPE_NULL;
    454 
    455         int n = a.getIndexCount();
    456         for (int i = 0; i < n; i++) {
    457             int attr = a.getIndex(i);
    458 
    459             switch (attr) {
    460             case com.android.internal.R.styleable.TextView_editable:
    461                 editable = a.getBoolean(attr, editable);
    462                 break;
    463 
    464             case com.android.internal.R.styleable.TextView_inputMethod:
    465                 inputMethod = a.getText(attr);
    466                 break;
    467 
    468             case com.android.internal.R.styleable.TextView_numeric:
    469                 numeric = a.getInt(attr, numeric);
    470                 break;
    471 
    472             case com.android.internal.R.styleable.TextView_digits:
    473                 digits = a.getText(attr);
    474                 break;
    475 
    476             case com.android.internal.R.styleable.TextView_phoneNumber:
    477                 phone = a.getBoolean(attr, phone);
    478                 break;
    479 
    480             case com.android.internal.R.styleable.TextView_autoText:
    481                 autotext = a.getBoolean(attr, autotext);
    482                 break;
    483 
    484             case com.android.internal.R.styleable.TextView_capitalize:
    485                 autocap = a.getInt(attr, autocap);
    486                 break;
    487 
    488             case com.android.internal.R.styleable.TextView_bufferType:
    489                 buffertype = a.getInt(attr, buffertype);
    490                 break;
    491 
    492             case com.android.internal.R.styleable.TextView_selectAllOnFocus:
    493                 selectallonfocus = a.getBoolean(attr, selectallonfocus);
    494                 break;
    495 
    496             case com.android.internal.R.styleable.TextView_autoLink:
    497                 mAutoLinkMask = a.getInt(attr, 0);
    498                 break;
    499 
    500             case com.android.internal.R.styleable.TextView_linksClickable:
    501                 mLinksClickable = a.getBoolean(attr, true);
    502                 break;
    503 
    504             case com.android.internal.R.styleable.TextView_drawableLeft:
    505                 drawableLeft = a.getDrawable(attr);
    506                 break;
    507 
    508             case com.android.internal.R.styleable.TextView_drawableTop:
    509                 drawableTop = a.getDrawable(attr);
    510                 break;
    511 
    512             case com.android.internal.R.styleable.TextView_drawableRight:
    513                 drawableRight = a.getDrawable(attr);
    514                 break;
    515 
    516             case com.android.internal.R.styleable.TextView_drawableBottom:
    517                 drawableBottom = a.getDrawable(attr);
    518                 break;
    519 
    520             case com.android.internal.R.styleable.TextView_drawablePadding:
    521                 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
    522                 break;
    523 
    524             case com.android.internal.R.styleable.TextView_maxLines:
    525                 setMaxLines(a.getInt(attr, -1));
    526                 break;
    527 
    528             case com.android.internal.R.styleable.TextView_maxHeight:
    529                 setMaxHeight(a.getDimensionPixelSize(attr, -1));
    530                 break;
    531 
    532             case com.android.internal.R.styleable.TextView_lines:
    533                 setLines(a.getInt(attr, -1));
    534                 break;
    535 
    536             case com.android.internal.R.styleable.TextView_height:
    537                 setHeight(a.getDimensionPixelSize(attr, -1));
    538                 break;
    539 
    540             case com.android.internal.R.styleable.TextView_minLines:
    541                 setMinLines(a.getInt(attr, -1));
    542                 break;
    543 
    544             case com.android.internal.R.styleable.TextView_minHeight:
    545                 setMinHeight(a.getDimensionPixelSize(attr, -1));
    546                 break;
    547 
    548             case com.android.internal.R.styleable.TextView_maxEms:
    549                 setMaxEms(a.getInt(attr, -1));
    550                 break;
    551 
    552             case com.android.internal.R.styleable.TextView_maxWidth:
    553                 setMaxWidth(a.getDimensionPixelSize(attr, -1));
    554                 break;
    555 
    556             case com.android.internal.R.styleable.TextView_ems:
    557                 setEms(a.getInt(attr, -1));
    558                 break;
    559 
    560             case com.android.internal.R.styleable.TextView_width:
    561                 setWidth(a.getDimensionPixelSize(attr, -1));
    562                 break;
    563 
    564             case com.android.internal.R.styleable.TextView_minEms:
    565                 setMinEms(a.getInt(attr, -1));
    566                 break;
    567 
    568             case com.android.internal.R.styleable.TextView_minWidth:
    569                 setMinWidth(a.getDimensionPixelSize(attr, -1));
    570                 break;
    571 
    572             case com.android.internal.R.styleable.TextView_gravity:
    573                 setGravity(a.getInt(attr, -1));
    574                 break;
    575 
    576             case com.android.internal.R.styleable.TextView_hint:
    577                 hint = a.getText(attr);
    578                 break;
    579 
    580             case com.android.internal.R.styleable.TextView_text:
    581                 text = a.getText(attr);
    582                 break;
    583 
    584             case com.android.internal.R.styleable.TextView_scrollHorizontally:
    585                 if (a.getBoolean(attr, false)) {
    586                     setHorizontallyScrolling(true);
    587                 }
    588                 break;
    589 
    590             case com.android.internal.R.styleable.TextView_singleLine:
    591                 singleLine = a.getBoolean(attr, singleLine);
    592                 break;
    593 
    594             case com.android.internal.R.styleable.TextView_ellipsize:
    595                 ellipsize = a.getInt(attr, ellipsize);
    596                 break;
    597 
    598             case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
    599                 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
    600                 break;
    601 
    602             case com.android.internal.R.styleable.TextView_includeFontPadding:
    603                 if (!a.getBoolean(attr, true)) {
    604                     setIncludeFontPadding(false);
    605                 }
    606                 break;
    607 
    608             case com.android.internal.R.styleable.TextView_cursorVisible:
    609                 if (!a.getBoolean(attr, true)) {
    610                     setCursorVisible(false);
    611                 }
    612                 break;
    613 
    614             case com.android.internal.R.styleable.TextView_maxLength:
    615                 maxlength = a.getInt(attr, -1);
    616                 break;
    617 
    618             case com.android.internal.R.styleable.TextView_textScaleX:
    619                 setTextScaleX(a.getFloat(attr, 1.0f));
    620                 break;
    621 
    622             case com.android.internal.R.styleable.TextView_freezesText:
    623                 mFreezesText = a.getBoolean(attr, false);
    624                 break;
    625 
    626             case com.android.internal.R.styleable.TextView_shadowColor:
    627                 shadowcolor = a.getInt(attr, 0);
    628                 break;
    629 
    630             case com.android.internal.R.styleable.TextView_shadowDx:
    631                 dx = a.getFloat(attr, 0);
    632                 break;
    633 
    634             case com.android.internal.R.styleable.TextView_shadowDy:
    635                 dy = a.getFloat(attr, 0);
    636                 break;
    637 
    638             case com.android.internal.R.styleable.TextView_shadowRadius:
    639                 r = a.getFloat(attr, 0);
    640                 break;
    641 
    642             case com.android.internal.R.styleable.TextView_enabled:
    643                 setEnabled(a.getBoolean(attr, isEnabled()));
    644                 break;
    645 
    646             case com.android.internal.R.styleable.TextView_textColorHighlight:
    647                 textColorHighlight = a.getColor(attr, textColorHighlight);
    648                 break;
    649 
    650             case com.android.internal.R.styleable.TextView_textColor:
    651                 textColor = a.getColorStateList(attr);
    652                 break;
    653 
    654             case com.android.internal.R.styleable.TextView_textColorHint:
    655                 textColorHint = a.getColorStateList(attr);
    656                 break;
    657 
    658             case com.android.internal.R.styleable.TextView_textColorLink:
    659                 textColorLink = a.getColorStateList(attr);
    660                 break;
    661 
    662             case com.android.internal.R.styleable.TextView_textSize:
    663                 textSize = a.getDimensionPixelSize(attr, textSize);
    664                 break;
    665 
    666             case com.android.internal.R.styleable.TextView_typeface:
    667                 typefaceIndex = a.getInt(attr, typefaceIndex);
    668                 break;
    669 
    670             case com.android.internal.R.styleable.TextView_textStyle:
    671                 styleIndex = a.getInt(attr, styleIndex);
    672                 break;
    673 
    674             case com.android.internal.R.styleable.TextView_password:
    675                 password = a.getBoolean(attr, password);
    676                 break;
    677 
    678             case com.android.internal.R.styleable.TextView_lineSpacingExtra:
    679                 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
    680                 break;
    681 
    682             case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
    683                 mSpacingMult = a.getFloat(attr, mSpacingMult);
    684                 break;
    685 
    686             case com.android.internal.R.styleable.TextView_inputType:
    687                 inputType = a.getInt(attr, mInputType);
    688                 break;
    689 
    690             case com.android.internal.R.styleable.TextView_imeOptions:
    691                 if (mInputContentType == null) {
    692                     mInputContentType = new InputContentType();
    693                 }
    694                 mInputContentType.imeOptions = a.getInt(attr,
    695                         mInputContentType.imeOptions);
    696                 break;
    697 
    698             case com.android.internal.R.styleable.TextView_imeActionLabel:
    699                 if (mInputContentType == null) {
    700                     mInputContentType = new InputContentType();
    701                 }
    702                 mInputContentType.imeActionLabel = a.getText(attr);
    703                 break;
    704 
    705             case com.android.internal.R.styleable.TextView_imeActionId:
    706                 if (mInputContentType == null) {
    707                     mInputContentType = new InputContentType();
    708                 }
    709                 mInputContentType.imeActionId = a.getInt(attr,
    710                         mInputContentType.imeActionId);
    711                 break;
    712 
    713             case com.android.internal.R.styleable.TextView_privateImeOptions:
    714                 setPrivateImeOptions(a.getString(attr));
    715                 break;
    716 
    717             case com.android.internal.R.styleable.TextView_editorExtras:
    718                 try {
    719                     setInputExtras(a.getResourceId(attr, 0));
    720                 } catch (XmlPullParserException e) {
    721                     Log.w(LOG_TAG, "Failure reading input extras", e);
    722                 } catch (IOException e) {
    723                     Log.w(LOG_TAG, "Failure reading input extras", e);
    724                 }
    725                 break;
    726 
    727             case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
    728                 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
    729                 break;
    730 
    731             case com.android.internal.R.styleable.TextView_textSelectHandleRight:
    732                 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
    733                 break;
    734 
    735             case com.android.internal.R.styleable.TextView_textSelectHandle:
    736                 mTextSelectHandleRes = a.getResourceId(attr, 0);
    737                 break;
    738             }
    739         }
    740         a.recycle();
    741 
    742         BufferType bufferType = BufferType.EDITABLE;
    743 
    744         if ((inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
    745                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
    746             password = true;
    747         }
    748 
    749         if (inputMethod != null) {
    750             Class<?> c;
    751 
    752             try {
    753                 c = Class.forName(inputMethod.toString());
    754             } catch (ClassNotFoundException ex) {
    755                 throw new RuntimeException(ex);
    756             }
    757 
    758             try {
    759                 mInput = (KeyListener) c.newInstance();
    760             } catch (InstantiationException ex) {
    761                 throw new RuntimeException(ex);
    762             } catch (IllegalAccessException ex) {
    763                 throw new RuntimeException(ex);
    764             }
    765             try {
    766                 mInputType = inputType != EditorInfo.TYPE_NULL
    767                         ? inputType
    768                         : mInput.getInputType();
    769             } catch (IncompatibleClassChangeError e) {
    770                 mInputType = EditorInfo.TYPE_CLASS_TEXT;
    771             }
    772         } else if (digits != null) {
    773             mInput = DigitsKeyListener.getInstance(digits.toString());
    774             // If no input type was specified, we will default to generic
    775             // text, since we can't tell the IME about the set of digits
    776             // that was selected.
    777             mInputType = inputType != EditorInfo.TYPE_NULL
    778                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
    779         } else if (inputType != EditorInfo.TYPE_NULL) {
    780             setInputType(inputType, true);
    781             singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
    782                             | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
    783                     (EditorInfo.TYPE_CLASS_TEXT
    784                             | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
    785         } else if (phone) {
    786             mInput = DialerKeyListener.getInstance();
    787             mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
    788         } else if (numeric != 0) {
    789             mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
    790                                                    (numeric & DECIMAL) != 0);
    791             inputType = EditorInfo.TYPE_CLASS_NUMBER;
    792             if ((numeric & SIGNED) != 0) {
    793                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
    794             }
    795             if ((numeric & DECIMAL) != 0) {
    796                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
    797             }
    798             mInputType = inputType;
    799         } else if (autotext || autocap != -1) {
    800             TextKeyListener.Capitalize cap;
    801 
    802             inputType = EditorInfo.TYPE_CLASS_TEXT;
    803             if (!singleLine) {
    804                 inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
    805             }
    806 
    807             switch (autocap) {
    808             case 1:
    809                 cap = TextKeyListener.Capitalize.SENTENCES;
    810                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
    811                 break;
    812 
    813             case 2:
    814                 cap = TextKeyListener.Capitalize.WORDS;
    815                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
    816                 break;
    817 
    818             case 3:
    819                 cap = TextKeyListener.Capitalize.CHARACTERS;
    820                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
    821                 break;
    822 
    823             default:
    824                 cap = TextKeyListener.Capitalize.NONE;
    825                 break;
    826             }
    827 
    828             mInput = TextKeyListener.getInstance(autotext, cap);
    829             mInputType = inputType;
    830         } else if (editable) {
    831             mInput = TextKeyListener.getInstance();
    832             mInputType = EditorInfo.TYPE_CLASS_TEXT;
    833             if (!singleLine) {
    834                 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
    835             }
    836         } else {
    837             mInput = null;
    838 
    839             switch (buffertype) {
    840                 case 0:
    841                     bufferType = BufferType.NORMAL;
    842                     break;
    843                 case 1:
    844                     bufferType = BufferType.SPANNABLE;
    845                     break;
    846                 case 2:
    847                     bufferType = BufferType.EDITABLE;
    848                     break;
    849             }
    850         }
    851 
    852         if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS)
    853                 == EditorInfo.TYPE_CLASS_TEXT) {
    854             mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
    855                 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
    856         }
    857 
    858         if (selectallonfocus) {
    859             mSelectAllOnFocus = true;
    860 
    861             if (bufferType == BufferType.NORMAL)
    862                 bufferType = BufferType.SPANNABLE;
    863         }
    864 
    865         setCompoundDrawablesWithIntrinsicBounds(
    866             drawableLeft, drawableTop, drawableRight, drawableBottom);
    867         setCompoundDrawablePadding(drawablePadding);
    868 
    869         if (singleLine) {
    870             setSingleLine();
    871 
    872             if (mInput == null && ellipsize < 0) {
    873                 ellipsize = 3; // END
    874             }
    875         }
    876 
    877         switch (ellipsize) {
    878             case 1:
    879                 setEllipsize(TextUtils.TruncateAt.START);
    880                 break;
    881             case 2:
    882                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
    883                 break;
    884             case 3:
    885                 setEllipsize(TextUtils.TruncateAt.END);
    886                 break;
    887             case 4:
    888                 setHorizontalFadingEdgeEnabled(true);
    889                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
    890                 break;
    891         }
    892 
    893         setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
    894         setHintTextColor(textColorHint);
    895         setLinkTextColor(textColorLink);
    896         if (textColorHighlight != 0) {
    897             setHighlightColor(textColorHighlight);
    898         }
    899         setRawTextSize(textSize);
    900 
    901         if (password) {
    902             setTransformationMethod(PasswordTransformationMethod.getInstance());
    903             typefaceIndex = MONOSPACE;
    904         } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS
    905                 |EditorInfo.TYPE_MASK_VARIATION))
    906                 == (EditorInfo.TYPE_CLASS_TEXT
    907                         |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
    908             typefaceIndex = MONOSPACE;
    909         }
    910 
    911         setTypefaceByIndex(typefaceIndex, styleIndex);
    912 
    913         if (shadowcolor != 0) {
    914             setShadowLayer(r, dx, dy, shadowcolor);
    915         }
    916 
    917         if (maxlength >= 0) {
    918             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
    919         } else {
    920             setFilters(NO_FILTERS);
    921         }
    922 
    923         setText(text, bufferType);
    924         if (hint != null) setHint(hint);
    925 
    926         /*
    927          * Views are not normally focusable unless specified to be.
    928          * However, TextViews that have input or movement methods *are*
    929          * focusable by default.
    930          */
    931         a = context.obtainStyledAttributes(attrs,
    932                                            com.android.internal.R.styleable.View,
    933                                            defStyle, 0);
    934 
    935         boolean focusable = mMovement != null || mInput != null;
    936         boolean clickable = focusable;
    937         boolean longClickable = focusable;
    938 
    939         n = a.getIndexCount();
    940         for (int i = 0; i < n; i++) {
    941             int attr = a.getIndex(i);
    942 
    943             switch (attr) {
    944             case com.android.internal.R.styleable.View_focusable:
    945                 focusable = a.getBoolean(attr, focusable);
    946                 break;
    947 
    948             case com.android.internal.R.styleable.View_clickable:
    949                 clickable = a.getBoolean(attr, clickable);
    950                 break;
    951 
    952             case com.android.internal.R.styleable.View_longClickable:
    953                 longClickable = a.getBoolean(attr, longClickable);
    954                 break;
    955             }
    956         }
    957         a.recycle();
    958 
    959         setFocusable(focusable);
    960         setClickable(clickable);
    961         setLongClickable(longClickable);
    962 
    963         prepareCursorControllers();
    964     }
    965 
    966     private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
    967         Typeface tf = null;
    968         switch (typefaceIndex) {
    969             case SANS:
    970                 tf = Typeface.SANS_SERIF;
    971                 break;
    972 
    973             case SERIF:
    974                 tf = Typeface.SERIF;
    975                 break;
    976 
    977             case MONOSPACE:
    978                 tf = Typeface.MONOSPACE;
    979                 break;
    980         }
    981 
    982         setTypeface(tf, styleIndex);
    983     }
    984 
    985     /**
    986      * Sets the typeface and style in which the text should be displayed,
    987      * and turns on the fake bold and italic bits in the Paint if the
    988      * Typeface that you provided does not have all the bits in the
    989      * style that you specified.
    990      *
    991      * @attr ref android.R.styleable#TextView_typeface
    992      * @attr ref android.R.styleable#TextView_textStyle
    993      */
    994     public void setTypeface(Typeface tf, int style) {
    995         if (style > 0) {
    996             if (tf == null) {
    997                 tf = Typeface.defaultFromStyle(style);
    998             } else {
    999                 tf = Typeface.create(tf, style);
   1000             }
   1001 
   1002             setTypeface(tf);
   1003             // now compute what (if any) algorithmic styling is needed
   1004             int typefaceStyle = tf != null ? tf.getStyle() : 0;
   1005             int need = style & ~typefaceStyle;
   1006             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
   1007             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
   1008         } else {
   1009             mTextPaint.setFakeBoldText(false);
   1010             mTextPaint.setTextSkewX(0);
   1011             setTypeface(tf);
   1012         }
   1013     }
   1014 
   1015     /**
   1016      * Subclasses override this to specify that they have a KeyListener
   1017      * by default even if not specifically called for in the XML options.
   1018      */
   1019     protected boolean getDefaultEditable() {
   1020         return false;
   1021     }
   1022 
   1023     /**
   1024      * Subclasses override this to specify a default movement method.
   1025      */
   1026     protected MovementMethod getDefaultMovementMethod() {
   1027         return null;
   1028     }
   1029 
   1030     /**
   1031      * Return the text the TextView is displaying. If setText() was called with
   1032      * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
   1033      * the return value from this method to Spannable or Editable, respectively.
   1034      *
   1035      * Note: The content of the return value should not be modified. If you want
   1036      * a modifiable one, you should make your own copy first.
   1037      */
   1038     @ViewDebug.CapturedViewProperty
   1039     public CharSequence getText() {
   1040         return mText;
   1041     }
   1042 
   1043     /**
   1044      * Returns the length, in characters, of the text managed by this TextView
   1045      */
   1046     public int length() {
   1047         return mText.length();
   1048     }
   1049 
   1050     /**
   1051      * Return the text the TextView is displaying as an Editable object.  If
   1052      * the text is not editable, null is returned.
   1053      *
   1054      * @see #getText
   1055      */
   1056     public Editable getEditableText() {
   1057         return (mText instanceof Editable) ? (Editable)mText : null;
   1058     }
   1059 
   1060     /**
   1061      * @return the height of one standard line in pixels.  Note that markup
   1062      * within the text can cause individual lines to be taller or shorter
   1063      * than this height, and the layout may contain additional first-
   1064      * or last-line padding.
   1065      */
   1066     public int getLineHeight() {
   1067         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
   1068                           + mSpacingAdd);
   1069     }
   1070 
   1071     /**
   1072      * @return the Layout that is currently being used to display the text.
   1073      * This can be null if the text or width has recently changes.
   1074      */
   1075     public final Layout getLayout() {
   1076         return mLayout;
   1077     }
   1078 
   1079     /**
   1080      * @return the current key listener for this TextView.
   1081      * This will frequently be null for non-EditText TextViews.
   1082      */
   1083     public final KeyListener getKeyListener() {
   1084         return mInput;
   1085     }
   1086 
   1087     /**
   1088      * Sets the key listener to be used with this TextView.  This can be null
   1089      * to disallow user input.  Note that this method has significant and
   1090      * subtle interactions with soft keyboards and other input method:
   1091      * see {@link KeyListener#getInputType() KeyListener.getContentType()}
   1092      * for important details.  Calling this method will replace the current
   1093      * content type of the text view with the content type returned by the
   1094      * key listener.
   1095      * <p>
   1096      * Be warned that if you want a TextView with a key listener or movement
   1097      * method not to be focusable, or if you want a TextView without a
   1098      * key listener or movement method to be focusable, you must call
   1099      * {@link #setFocusable} again after calling this to get the focusability
   1100      * back the way you want it.
   1101      *
   1102      * @attr ref android.R.styleable#TextView_numeric
   1103      * @attr ref android.R.styleable#TextView_digits
   1104      * @attr ref android.R.styleable#TextView_phoneNumber
   1105      * @attr ref android.R.styleable#TextView_inputMethod
   1106      * @attr ref android.R.styleable#TextView_capitalize
   1107      * @attr ref android.R.styleable#TextView_autoText
   1108      */
   1109     public void setKeyListener(KeyListener input) {
   1110         setKeyListenerOnly(input);
   1111         fixFocusableAndClickableSettings();
   1112 
   1113         if (input != null) {
   1114             try {
   1115                 mInputType = mInput.getInputType();
   1116             } catch (IncompatibleClassChangeError e) {
   1117                 mInputType = EditorInfo.TYPE_CLASS_TEXT;
   1118             }
   1119             if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
   1120                     == EditorInfo.TYPE_CLASS_TEXT) {
   1121                 if (mSingleLine) {
   1122                     mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   1123                 } else {
   1124                     mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   1125                 }
   1126             }
   1127         } else {
   1128             mInputType = EditorInfo.TYPE_NULL;
   1129         }
   1130 
   1131         InputMethodManager imm = InputMethodManager.peekInstance();
   1132         if (imm != null) imm.restartInput(this);
   1133     }
   1134 
   1135     private void setKeyListenerOnly(KeyListener input) {
   1136         mInput = input;
   1137         if (mInput != null && !(mText instanceof Editable))
   1138             setText(mText);
   1139 
   1140         setFilters((Editable) mText, mFilters);
   1141     }
   1142 
   1143     /**
   1144      * @return the movement method being used for this TextView.
   1145      * This will frequently be null for non-EditText TextViews.
   1146      */
   1147     public final MovementMethod getMovementMethod() {
   1148         return mMovement;
   1149     }
   1150 
   1151     /**
   1152      * Sets the movement method (arrow key handler) to be used for
   1153      * this TextView.  This can be null to disallow using the arrow keys
   1154      * to move the cursor or scroll the view.
   1155      * <p>
   1156      * Be warned that if you want a TextView with a key listener or movement
   1157      * method not to be focusable, or if you want a TextView without a
   1158      * key listener or movement method to be focusable, you must call
   1159      * {@link #setFocusable} again after calling this to get the focusability
   1160      * back the way you want it.
   1161      */
   1162     public final void setMovementMethod(MovementMethod movement) {
   1163         mMovement = movement;
   1164 
   1165         if (mMovement != null && !(mText instanceof Spannable))
   1166             setText(mText);
   1167 
   1168         fixFocusableAndClickableSettings();
   1169 
   1170         // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
   1171         prepareCursorControllers();
   1172     }
   1173 
   1174     private void fixFocusableAndClickableSettings() {
   1175         if ((mMovement != null) || mInput != null) {
   1176             setFocusable(true);
   1177             setClickable(true);
   1178             setLongClickable(true);
   1179         } else {
   1180             setFocusable(false);
   1181             setClickable(false);
   1182             setLongClickable(false);
   1183         }
   1184     }
   1185 
   1186     /**
   1187      * @return the current transformation method for this TextView.
   1188      * This will frequently be null except for single-line and password
   1189      * fields.
   1190      */
   1191     public final TransformationMethod getTransformationMethod() {
   1192         return mTransformation;
   1193     }
   1194 
   1195     /**
   1196      * Sets the transformation that is applied to the text that this
   1197      * TextView is displaying.
   1198      *
   1199      * @attr ref android.R.styleable#TextView_password
   1200      * @attr ref android.R.styleable#TextView_singleLine
   1201      */
   1202     public final void setTransformationMethod(TransformationMethod method) {
   1203         if (method == mTransformation) {
   1204             // Avoid the setText() below if the transformation is
   1205             // the same.
   1206             return;
   1207         }
   1208         if (mTransformation != null) {
   1209             if (mText instanceof Spannable) {
   1210                 ((Spannable) mText).removeSpan(mTransformation);
   1211             }
   1212         }
   1213 
   1214         mTransformation = method;
   1215 
   1216         setText(mText);
   1217     }
   1218 
   1219     /**
   1220      * Returns the top padding of the view, plus space for the top
   1221      * Drawable if any.
   1222      */
   1223     public int getCompoundPaddingTop() {
   1224         final Drawables dr = mDrawables;
   1225         if (dr == null || dr.mDrawableTop == null) {
   1226             return mPaddingTop;
   1227         } else {
   1228             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
   1229         }
   1230     }
   1231 
   1232     /**
   1233      * Returns the bottom padding of the view, plus space for the bottom
   1234      * Drawable if any.
   1235      */
   1236     public int getCompoundPaddingBottom() {
   1237         final Drawables dr = mDrawables;
   1238         if (dr == null || dr.mDrawableBottom == null) {
   1239             return mPaddingBottom;
   1240         } else {
   1241             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
   1242         }
   1243     }
   1244 
   1245     /**
   1246      * Returns the left padding of the view, plus space for the left
   1247      * Drawable if any.
   1248      */
   1249     public int getCompoundPaddingLeft() {
   1250         final Drawables dr = mDrawables;
   1251         if (dr == null || dr.mDrawableLeft == null) {
   1252             return mPaddingLeft;
   1253         } else {
   1254             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
   1255         }
   1256     }
   1257 
   1258     /**
   1259      * Returns the right padding of the view, plus space for the right
   1260      * Drawable if any.
   1261      */
   1262     public int getCompoundPaddingRight() {
   1263         final Drawables dr = mDrawables;
   1264         if (dr == null || dr.mDrawableRight == null) {
   1265             return mPaddingRight;
   1266         } else {
   1267             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
   1268         }
   1269     }
   1270 
   1271     /**
   1272      * Returns the extended top padding of the view, including both the
   1273      * top Drawable if any and any extra space to keep more than maxLines
   1274      * of text from showing.  It is only valid to call this after measuring.
   1275      */
   1276     public int getExtendedPaddingTop() {
   1277         if (mMaxMode != LINES) {
   1278             return getCompoundPaddingTop();
   1279         }
   1280 
   1281         if (mLayout.getLineCount() <= mMaximum) {
   1282             return getCompoundPaddingTop();
   1283         }
   1284 
   1285         int top = getCompoundPaddingTop();
   1286         int bottom = getCompoundPaddingBottom();
   1287         int viewht = getHeight() - top - bottom;
   1288         int layoutht = mLayout.getLineTop(mMaximum);
   1289 
   1290         if (layoutht >= viewht) {
   1291             return top;
   1292         }
   1293 
   1294         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   1295         if (gravity == Gravity.TOP) {
   1296             return top;
   1297         } else if (gravity == Gravity.BOTTOM) {
   1298             return top + viewht - layoutht;
   1299         } else { // (gravity == Gravity.CENTER_VERTICAL)
   1300             return top + (viewht - layoutht) / 2;
   1301         }
   1302     }
   1303 
   1304     /**
   1305      * Returns the extended bottom padding of the view, including both the
   1306      * bottom Drawable if any and any extra space to keep more than maxLines
   1307      * of text from showing.  It is only valid to call this after measuring.
   1308      */
   1309     public int getExtendedPaddingBottom() {
   1310         if (mMaxMode != LINES) {
   1311             return getCompoundPaddingBottom();
   1312         }
   1313 
   1314         if (mLayout.getLineCount() <= mMaximum) {
   1315             return getCompoundPaddingBottom();
   1316         }
   1317 
   1318         int top = getCompoundPaddingTop();
   1319         int bottom = getCompoundPaddingBottom();
   1320         int viewht = getHeight() - top - bottom;
   1321         int layoutht = mLayout.getLineTop(mMaximum);
   1322 
   1323         if (layoutht >= viewht) {
   1324             return bottom;
   1325         }
   1326 
   1327         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   1328         if (gravity == Gravity.TOP) {
   1329             return bottom + viewht - layoutht;
   1330         } else if (gravity == Gravity.BOTTOM) {
   1331             return bottom;
   1332         } else { // (gravity == Gravity.CENTER_VERTICAL)
   1333             return bottom + (viewht - layoutht) / 2;
   1334         }
   1335     }
   1336 
   1337     /**
   1338      * Returns the total left padding of the view, including the left
   1339      * Drawable if any.
   1340      */
   1341     public int getTotalPaddingLeft() {
   1342         return getCompoundPaddingLeft();
   1343     }
   1344 
   1345     /**
   1346      * Returns the total right padding of the view, including the right
   1347      * Drawable if any.
   1348      */
   1349     public int getTotalPaddingRight() {
   1350         return getCompoundPaddingRight();
   1351     }
   1352 
   1353     /**
   1354      * Returns the total top padding of the view, including the top
   1355      * Drawable if any, the extra space to keep more than maxLines
   1356      * from showing, and the vertical offset for gravity, if any.
   1357      */
   1358     public int getTotalPaddingTop() {
   1359         return getExtendedPaddingTop() + getVerticalOffset(true);
   1360     }
   1361 
   1362     /**
   1363      * Returns the total bottom padding of the view, including the bottom
   1364      * Drawable if any, the extra space to keep more than maxLines
   1365      * from showing, and the vertical offset for gravity, if any.
   1366      */
   1367     public int getTotalPaddingBottom() {
   1368         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
   1369     }
   1370 
   1371     /**
   1372      * Sets the Drawables (if any) to appear to the left of, above,
   1373      * to the right of, and below the text.  Use null if you do not
   1374      * want a Drawable there.  The Drawables must already have had
   1375      * {@link Drawable#setBounds} called.
   1376      *
   1377      * @attr ref android.R.styleable#TextView_drawableLeft
   1378      * @attr ref android.R.styleable#TextView_drawableTop
   1379      * @attr ref android.R.styleable#TextView_drawableRight
   1380      * @attr ref android.R.styleable#TextView_drawableBottom
   1381      */
   1382     public void setCompoundDrawables(Drawable left, Drawable top,
   1383                                      Drawable right, Drawable bottom) {
   1384         Drawables dr = mDrawables;
   1385 
   1386         final boolean drawables = left != null || top != null
   1387                 || right != null || bottom != null;
   1388 
   1389         if (!drawables) {
   1390             // Clearing drawables...  can we free the data structure?
   1391             if (dr != null) {
   1392                 if (dr.mDrawablePadding == 0) {
   1393                     mDrawables = null;
   1394                 } else {
   1395                     // We need to retain the last set padding, so just clear
   1396                     // out all of the fields in the existing structure.
   1397                     if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
   1398                     dr.mDrawableLeft = null;
   1399                     if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
   1400                     dr.mDrawableTop = null;
   1401                     if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
   1402                     dr.mDrawableRight = null;
   1403                     if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
   1404                     dr.mDrawableBottom = null;
   1405                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   1406                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   1407                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   1408                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   1409                 }
   1410             }
   1411         } else {
   1412             if (dr == null) {
   1413                 mDrawables = dr = new Drawables();
   1414             }
   1415 
   1416             if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
   1417                 dr.mDrawableLeft.setCallback(null);
   1418             }
   1419             dr.mDrawableLeft = left;
   1420 
   1421             if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
   1422                 dr.mDrawableTop.setCallback(null);
   1423             }
   1424             dr.mDrawableTop = top;
   1425 
   1426             if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
   1427                 dr.mDrawableRight.setCallback(null);
   1428             }
   1429             dr.mDrawableRight = right;
   1430 
   1431             if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
   1432                 dr.mDrawableBottom.setCallback(null);
   1433             }
   1434             dr.mDrawableBottom = bottom;
   1435 
   1436             final Rect compoundRect = dr.mCompoundRect;
   1437             int[] state;
   1438 
   1439             state = getDrawableState();
   1440 
   1441             if (left != null) {
   1442                 left.setState(state);
   1443                 left.copyBounds(compoundRect);
   1444                 left.setCallback(this);
   1445                 dr.mDrawableSizeLeft = compoundRect.width();
   1446                 dr.mDrawableHeightLeft = compoundRect.height();
   1447             } else {
   1448                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   1449             }
   1450 
   1451             if (right != null) {
   1452                 right.setState(state);
   1453                 right.copyBounds(compoundRect);
   1454                 right.setCallback(this);
   1455                 dr.mDrawableSizeRight = compoundRect.width();
   1456                 dr.mDrawableHeightRight = compoundRect.height();
   1457             } else {
   1458                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   1459             }
   1460 
   1461             if (top != null) {
   1462                 top.setState(state);
   1463                 top.copyBounds(compoundRect);
   1464                 top.setCallback(this);
   1465                 dr.mDrawableSizeTop = compoundRect.height();
   1466                 dr.mDrawableWidthTop = compoundRect.width();
   1467             } else {
   1468                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   1469             }
   1470 
   1471             if (bottom != null) {
   1472                 bottom.setState(state);
   1473                 bottom.copyBounds(compoundRect);
   1474                 bottom.setCallback(this);
   1475                 dr.mDrawableSizeBottom = compoundRect.height();
   1476                 dr.mDrawableWidthBottom = compoundRect.width();
   1477             } else {
   1478                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   1479             }
   1480         }
   1481 
   1482         invalidate();
   1483         requestLayout();
   1484     }
   1485 
   1486     /**
   1487      * Sets the Drawables (if any) to appear to the left of, above,
   1488      * to the right of, and below the text.  Use 0 if you do not
   1489      * want a Drawable there. The Drawables' bounds will be set to
   1490      * their intrinsic bounds.
   1491      *
   1492      * @param left Resource identifier of the left Drawable.
   1493      * @param top Resource identifier of the top Drawable.
   1494      * @param right Resource identifier of the right Drawable.
   1495      * @param bottom Resource identifier of the bottom Drawable.
   1496      *
   1497      * @attr ref android.R.styleable#TextView_drawableLeft
   1498      * @attr ref android.R.styleable#TextView_drawableTop
   1499      * @attr ref android.R.styleable#TextView_drawableRight
   1500      * @attr ref android.R.styleable#TextView_drawableBottom
   1501      */
   1502     public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
   1503         final Resources resources = getContext().getResources();
   1504         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
   1505                 top != 0 ? resources.getDrawable(top) : null,
   1506                 right != 0 ? resources.getDrawable(right) : null,
   1507                 bottom != 0 ? resources.getDrawable(bottom) : null);
   1508     }
   1509 
   1510     /**
   1511      * Sets the Drawables (if any) to appear to the left of, above,
   1512      * to the right of, and below the text.  Use null if you do not
   1513      * want a Drawable there. The Drawables' bounds will be set to
   1514      * their intrinsic bounds.
   1515      *
   1516      * @attr ref android.R.styleable#TextView_drawableLeft
   1517      * @attr ref android.R.styleable#TextView_drawableTop
   1518      * @attr ref android.R.styleable#TextView_drawableRight
   1519      * @attr ref android.R.styleable#TextView_drawableBottom
   1520      */
   1521     public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
   1522             Drawable right, Drawable bottom) {
   1523 
   1524         if (left != null) {
   1525             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
   1526         }
   1527         if (right != null) {
   1528             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
   1529         }
   1530         if (top != null) {
   1531             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
   1532         }
   1533         if (bottom != null) {
   1534             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
   1535         }
   1536         setCompoundDrawables(left, top, right, bottom);
   1537     }
   1538 
   1539     /**
   1540      * Returns drawables for the left, top, right, and bottom borders.
   1541      */
   1542     public Drawable[] getCompoundDrawables() {
   1543         final Drawables dr = mDrawables;
   1544         if (dr != null) {
   1545             return new Drawable[] {
   1546                 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
   1547             };
   1548         } else {
   1549             return new Drawable[] { null, null, null, null };
   1550         }
   1551     }
   1552 
   1553     /**
   1554      * Sets the size of the padding between the compound drawables and
   1555      * the text.
   1556      *
   1557      * @attr ref android.R.styleable#TextView_drawablePadding
   1558      */
   1559     public void setCompoundDrawablePadding(int pad) {
   1560         Drawables dr = mDrawables;
   1561         if (pad == 0) {
   1562             if (dr != null) {
   1563                 dr.mDrawablePadding = pad;
   1564             }
   1565         } else {
   1566             if (dr == null) {
   1567                 mDrawables = dr = new Drawables();
   1568             }
   1569             dr.mDrawablePadding = pad;
   1570         }
   1571 
   1572         invalidate();
   1573         requestLayout();
   1574     }
   1575 
   1576     /**
   1577      * Returns the padding between the compound drawables and the text.
   1578      */
   1579     public int getCompoundDrawablePadding() {
   1580         final Drawables dr = mDrawables;
   1581         return dr != null ? dr.mDrawablePadding : 0;
   1582     }
   1583 
   1584     @Override
   1585     public void setPadding(int left, int top, int right, int bottom) {
   1586         if (left != mPaddingLeft ||
   1587             right != mPaddingRight ||
   1588             top != mPaddingTop ||
   1589             bottom != mPaddingBottom) {
   1590             nullLayouts();
   1591         }
   1592 
   1593         // the super call will requestLayout()
   1594         super.setPadding(left, top, right, bottom);
   1595         invalidate();
   1596     }
   1597 
   1598     /**
   1599      * Gets the autolink mask of the text.  See {@link
   1600      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
   1601      * possible values.
   1602      *
   1603      * @attr ref android.R.styleable#TextView_autoLink
   1604      */
   1605     public final int getAutoLinkMask() {
   1606         return mAutoLinkMask;
   1607     }
   1608 
   1609     /**
   1610      * Sets the text color, size, style, hint color, and highlight color
   1611      * from the specified TextAppearance resource.
   1612      */
   1613     public void setTextAppearance(Context context, int resid) {
   1614         TypedArray appearance =
   1615             context.obtainStyledAttributes(resid,
   1616                                            com.android.internal.R.styleable.TextAppearance);
   1617 
   1618         int color;
   1619         ColorStateList colors;
   1620         int ts;
   1621 
   1622         color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
   1623         if (color != 0) {
   1624             setHighlightColor(color);
   1625         }
   1626 
   1627         colors = appearance.getColorStateList(com.android.internal.R.styleable.
   1628                                               TextAppearance_textColor);
   1629         if (colors != null) {
   1630             setTextColor(colors);
   1631         }
   1632 
   1633         ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
   1634                                               TextAppearance_textSize, 0);
   1635         if (ts != 0) {
   1636             setRawTextSize(ts);
   1637         }
   1638 
   1639         colors = appearance.getColorStateList(com.android.internal.R.styleable.
   1640                                               TextAppearance_textColorHint);
   1641         if (colors != null) {
   1642             setHintTextColor(colors);
   1643         }
   1644 
   1645         colors = appearance.getColorStateList(com.android.internal.R.styleable.
   1646                                               TextAppearance_textColorLink);
   1647         if (colors != null) {
   1648             setLinkTextColor(colors);
   1649         }
   1650 
   1651         int typefaceIndex, styleIndex;
   1652 
   1653         typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
   1654                                           TextAppearance_typeface, -1);
   1655         styleIndex = appearance.getInt(com.android.internal.R.styleable.
   1656                                        TextAppearance_textStyle, -1);
   1657 
   1658         setTypefaceByIndex(typefaceIndex, styleIndex);
   1659         appearance.recycle();
   1660     }
   1661 
   1662     /**
   1663      * @return the size (in pixels) of the default text size in this TextView.
   1664      */
   1665     public float getTextSize() {
   1666         return mTextPaint.getTextSize();
   1667     }
   1668 
   1669     /**
   1670      * Set the default text size to the given value, interpreted as "scaled
   1671      * pixel" units.  This size is adjusted based on the current density and
   1672      * user font size preference.
   1673      *
   1674      * @param size The scaled pixel size.
   1675      *
   1676      * @attr ref android.R.styleable#TextView_textSize
   1677      */
   1678     @android.view.RemotableViewMethod
   1679     public void setTextSize(float size) {
   1680         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
   1681     }
   1682 
   1683     /**
   1684      * Set the default text size to a given unit and value.  See {@link
   1685      * TypedValue} for the possible dimension units.
   1686      *
   1687      * @param unit The desired dimension unit.
   1688      * @param size The desired size in the given units.
   1689      *
   1690      * @attr ref android.R.styleable#TextView_textSize
   1691      */
   1692     public void setTextSize(int unit, float size) {
   1693         Context c = getContext();
   1694         Resources r;
   1695 
   1696         if (c == null)
   1697             r = Resources.getSystem();
   1698         else
   1699             r = c.getResources();
   1700 
   1701         setRawTextSize(TypedValue.applyDimension(
   1702             unit, size, r.getDisplayMetrics()));
   1703     }
   1704 
   1705     private void setRawTextSize(float size) {
   1706         if (size != mTextPaint.getTextSize()) {
   1707             mTextPaint.setTextSize(size);
   1708 
   1709             if (mLayout != null) {
   1710                 nullLayouts();
   1711                 requestLayout();
   1712                 invalidate();
   1713             }
   1714         }
   1715     }
   1716 
   1717     /**
   1718      * @return the extent by which text is currently being stretched
   1719      * horizontally.  This will usually be 1.
   1720      */
   1721     public float getTextScaleX() {
   1722         return mTextPaint.getTextScaleX();
   1723     }
   1724 
   1725     /**
   1726      * Sets the extent by which text should be stretched horizontally.
   1727      *
   1728      * @attr ref android.R.styleable#TextView_textScaleX
   1729      */
   1730     @android.view.RemotableViewMethod
   1731     public void setTextScaleX(float size) {
   1732         if (size != mTextPaint.getTextScaleX()) {
   1733             mUserSetTextScaleX = true;
   1734             mTextPaint.setTextScaleX(size);
   1735 
   1736             if (mLayout != null) {
   1737                 nullLayouts();
   1738                 requestLayout();
   1739                 invalidate();
   1740             }
   1741         }
   1742     }
   1743 
   1744     /**
   1745      * Sets the typeface and style in which the text should be displayed.
   1746      * Note that not all Typeface families actually have bold and italic
   1747      * variants, so you may need to use
   1748      * {@link #setTypeface(Typeface, int)} to get the appearance
   1749      * that you actually want.
   1750      *
   1751      * @attr ref android.R.styleable#TextView_typeface
   1752      * @attr ref android.R.styleable#TextView_textStyle
   1753      */
   1754     public void setTypeface(Typeface tf) {
   1755         if (mTextPaint.getTypeface() != tf) {
   1756             mTextPaint.setTypeface(tf);
   1757 
   1758             if (mLayout != null) {
   1759                 nullLayouts();
   1760                 requestLayout();
   1761                 invalidate();
   1762             }
   1763         }
   1764     }
   1765 
   1766     /**
   1767      * @return the current typeface and style in which the text is being
   1768      * displayed.
   1769      */
   1770     public Typeface getTypeface() {
   1771         return mTextPaint.getTypeface();
   1772     }
   1773 
   1774     /**
   1775      * Sets the text color for all the states (normal, selected,
   1776      * focused) to be this color.
   1777      *
   1778      * @attr ref android.R.styleable#TextView_textColor
   1779      */
   1780     @android.view.RemotableViewMethod
   1781     public void setTextColor(int color) {
   1782         mTextColor = ColorStateList.valueOf(color);
   1783         updateTextColors();
   1784     }
   1785 
   1786     /**
   1787      * Sets the text color.
   1788      *
   1789      * @attr ref android.R.styleable#TextView_textColor
   1790      */
   1791     public void setTextColor(ColorStateList colors) {
   1792         if (colors == null) {
   1793             throw new NullPointerException();
   1794         }
   1795 
   1796         mTextColor = colors;
   1797         updateTextColors();
   1798     }
   1799 
   1800     /**
   1801      * Return the set of text colors.
   1802      *
   1803      * @return Returns the set of text colors.
   1804      */
   1805     public final ColorStateList getTextColors() {
   1806         return mTextColor;
   1807     }
   1808 
   1809     /**
   1810      * <p>Return the current color selected for normal text.</p>
   1811      *
   1812      * @return Returns the current text color.
   1813      */
   1814     public final int getCurrentTextColor() {
   1815         return mCurTextColor;
   1816     }
   1817 
   1818     /**
   1819      * Sets the color used to display the selection highlight.
   1820      *
   1821      * @attr ref android.R.styleable#TextView_textColorHighlight
   1822      */
   1823     @android.view.RemotableViewMethod
   1824     public void setHighlightColor(int color) {
   1825         if (mHighlightColor != color) {
   1826             mHighlightColor = color;
   1827             invalidate();
   1828         }
   1829     }
   1830 
   1831     /**
   1832      * Gives the text a shadow of the specified radius and color, the specified
   1833      * distance from its normal position.
   1834      *
   1835      * @attr ref android.R.styleable#TextView_shadowColor
   1836      * @attr ref android.R.styleable#TextView_shadowDx
   1837      * @attr ref android.R.styleable#TextView_shadowDy
   1838      * @attr ref android.R.styleable#TextView_shadowRadius
   1839      */
   1840     public void setShadowLayer(float radius, float dx, float dy, int color) {
   1841         mTextPaint.setShadowLayer(radius, dx, dy, color);
   1842 
   1843         mShadowRadius = radius;
   1844         mShadowDx = dx;
   1845         mShadowDy = dy;
   1846 
   1847         invalidate();
   1848     }
   1849 
   1850     /**
   1851      * @return the base paint used for the text.  Please use this only to
   1852      * consult the Paint's properties and not to change them.
   1853      */
   1854     public TextPaint getPaint() {
   1855         return mTextPaint;
   1856     }
   1857 
   1858     /**
   1859      * Sets the autolink mask of the text.  See {@link
   1860      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
   1861      * possible values.
   1862      *
   1863      * @attr ref android.R.styleable#TextView_autoLink
   1864      */
   1865     @android.view.RemotableViewMethod
   1866     public final void setAutoLinkMask(int mask) {
   1867         mAutoLinkMask = mask;
   1868     }
   1869 
   1870     /**
   1871      * Sets whether the movement method will automatically be set to
   1872      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   1873      * set to nonzero and links are detected in {@link #setText}.
   1874      * The default is true.
   1875      *
   1876      * @attr ref android.R.styleable#TextView_linksClickable
   1877      */
   1878     @android.view.RemotableViewMethod
   1879     public final void setLinksClickable(boolean whether) {
   1880         mLinksClickable = whether;
   1881     }
   1882 
   1883     /**
   1884      * Returns whether the movement method will automatically be set to
   1885      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   1886      * set to nonzero and links are detected in {@link #setText}.
   1887      * The default is true.
   1888      *
   1889      * @attr ref android.R.styleable#TextView_linksClickable
   1890      */
   1891     public final boolean getLinksClickable() {
   1892         return mLinksClickable;
   1893     }
   1894 
   1895     /**
   1896      * Returns the list of URLSpans attached to the text
   1897      * (by {@link Linkify} or otherwise) if any.  You can call
   1898      * {@link URLSpan#getURL} on them to find where they link to
   1899      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
   1900      * to find the region of the text they are attached to.
   1901      */
   1902     public URLSpan[] getUrls() {
   1903         if (mText instanceof Spanned) {
   1904             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
   1905         } else {
   1906             return new URLSpan[0];
   1907         }
   1908     }
   1909 
   1910     /**
   1911      * Sets the color of the hint text.
   1912      *
   1913      * @attr ref android.R.styleable#TextView_textColorHint
   1914      */
   1915     @android.view.RemotableViewMethod
   1916     public final void setHintTextColor(int color) {
   1917         mHintTextColor = ColorStateList.valueOf(color);
   1918         updateTextColors();
   1919     }
   1920 
   1921     /**
   1922      * Sets the color of the hint text.
   1923      *
   1924      * @attr ref android.R.styleable#TextView_textColorHint
   1925      */
   1926     public final void setHintTextColor(ColorStateList colors) {
   1927         mHintTextColor = colors;
   1928         updateTextColors();
   1929     }
   1930 
   1931     /**
   1932      * <p>Return the color used to paint the hint text.</p>
   1933      *
   1934      * @return Returns the list of hint text colors.
   1935      */
   1936     public final ColorStateList getHintTextColors() {
   1937         return mHintTextColor;
   1938     }
   1939 
   1940     /**
   1941      * <p>Return the current color selected to paint the hint text.</p>
   1942      *
   1943      * @return Returns the current hint text color.
   1944      */
   1945     public final int getCurrentHintTextColor() {
   1946         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
   1947     }
   1948 
   1949     /**
   1950      * Sets the color of links in the text.
   1951      *
   1952      * @attr ref android.R.styleable#TextView_textColorLink
   1953      */
   1954     @android.view.RemotableViewMethod
   1955     public final void setLinkTextColor(int color) {
   1956         mLinkTextColor = ColorStateList.valueOf(color);
   1957         updateTextColors();
   1958     }
   1959 
   1960     /**
   1961      * Sets the color of links in the text.
   1962      *
   1963      * @attr ref android.R.styleable#TextView_textColorLink
   1964      */
   1965     public final void setLinkTextColor(ColorStateList colors) {
   1966         mLinkTextColor = colors;
   1967         updateTextColors();
   1968     }
   1969 
   1970     /**
   1971      * <p>Returns the color used to paint links in the text.</p>
   1972      *
   1973      * @return Returns the list of link text colors.
   1974      */
   1975     public final ColorStateList getLinkTextColors() {
   1976         return mLinkTextColor;
   1977     }
   1978 
   1979     /**
   1980      * Sets the horizontal alignment of the text and the
   1981      * vertical gravity that will be used when there is extra space
   1982      * in the TextView beyond what is required for the text itself.
   1983      *
   1984      * @see android.view.Gravity
   1985      * @attr ref android.R.styleable#TextView_gravity
   1986      */
   1987     public void setGravity(int gravity) {
   1988         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
   1989             gravity |= Gravity.LEFT;
   1990         }
   1991         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
   1992             gravity |= Gravity.TOP;
   1993         }
   1994 
   1995         boolean newLayout = false;
   1996 
   1997         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
   1998             (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
   1999             newLayout = true;
   2000         }
   2001 
   2002         if (gravity != mGravity) {
   2003             invalidate();
   2004         }
   2005 
   2006         mGravity = gravity;
   2007 
   2008         if (mLayout != null && newLayout) {
   2009             // XXX this is heavy-handed because no actual content changes.
   2010             int want = mLayout.getWidth();
   2011             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   2012 
   2013             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   2014                           mRight - mLeft - getCompoundPaddingLeft() -
   2015                           getCompoundPaddingRight(), true);
   2016         }
   2017     }
   2018 
   2019     /**
   2020      * Returns the horizontal and vertical alignment of this TextView.
   2021      *
   2022      * @see android.view.Gravity
   2023      * @attr ref android.R.styleable#TextView_gravity
   2024      */
   2025     public int getGravity() {
   2026         return mGravity;
   2027     }
   2028 
   2029     /**
   2030      * @return the flags on the Paint being used to display the text.
   2031      * @see Paint#getFlags
   2032      */
   2033     public int getPaintFlags() {
   2034         return mTextPaint.getFlags();
   2035     }
   2036 
   2037     /**
   2038      * Sets flags on the Paint being used to display the text and
   2039      * reflows the text if they are different from the old flags.
   2040      * @see Paint#setFlags
   2041      */
   2042     @android.view.RemotableViewMethod
   2043     public void setPaintFlags(int flags) {
   2044         if (mTextPaint.getFlags() != flags) {
   2045             mTextPaint.setFlags(flags);
   2046 
   2047             if (mLayout != null) {
   2048                 nullLayouts();
   2049                 requestLayout();
   2050                 invalidate();
   2051             }
   2052         }
   2053     }
   2054 
   2055     /**
   2056      * Sets whether the text should be allowed to be wider than the
   2057      * View is.  If false, it will be wrapped to the width of the View.
   2058      *
   2059      * @attr ref android.R.styleable#TextView_scrollHorizontally
   2060      */
   2061     public void setHorizontallyScrolling(boolean whether) {
   2062         mHorizontallyScrolling = whether;
   2063 
   2064         if (mLayout != null) {
   2065             nullLayouts();
   2066             requestLayout();
   2067             invalidate();
   2068         }
   2069     }
   2070 
   2071     /**
   2072      * Makes the TextView at least this many lines tall
   2073      *
   2074      * @attr ref android.R.styleable#TextView_minLines
   2075      */
   2076     @android.view.RemotableViewMethod
   2077     public void setMinLines(int minlines) {
   2078         mMinimum = minlines;
   2079         mMinMode = LINES;
   2080 
   2081         requestLayout();
   2082         invalidate();
   2083     }
   2084 
   2085     /**
   2086      * Makes the TextView at least this many pixels tall
   2087      *
   2088      * @attr ref android.R.styleable#TextView_minHeight
   2089      */
   2090     @android.view.RemotableViewMethod
   2091     public void setMinHeight(int minHeight) {
   2092         mMinimum = minHeight;
   2093         mMinMode = PIXELS;
   2094 
   2095         requestLayout();
   2096         invalidate();
   2097     }
   2098 
   2099     /**
   2100      * Makes the TextView at most this many lines tall
   2101      *
   2102      * @attr ref android.R.styleable#TextView_maxLines
   2103      */
   2104     @android.view.RemotableViewMethod
   2105     public void setMaxLines(int maxlines) {
   2106         mMaximum = maxlines;
   2107         mMaxMode = LINES;
   2108 
   2109         requestLayout();
   2110         invalidate();
   2111     }
   2112 
   2113     /**
   2114      * Makes the TextView at most this many pixels tall
   2115      *
   2116      * @attr ref android.R.styleable#TextView_maxHeight
   2117      */
   2118     @android.view.RemotableViewMethod
   2119     public void setMaxHeight(int maxHeight) {
   2120         mMaximum = maxHeight;
   2121         mMaxMode = PIXELS;
   2122 
   2123         requestLayout();
   2124         invalidate();
   2125     }
   2126 
   2127     /**
   2128      * Makes the TextView exactly this many lines tall
   2129      *
   2130      * @attr ref android.R.styleable#TextView_lines
   2131      */
   2132     @android.view.RemotableViewMethod
   2133     public void setLines(int lines) {
   2134         mMaximum = mMinimum = lines;
   2135         mMaxMode = mMinMode = LINES;
   2136 
   2137         requestLayout();
   2138         invalidate();
   2139     }
   2140 
   2141     /**
   2142      * Makes the TextView exactly this many pixels tall.
   2143      * You could do the same thing by specifying this number in the
   2144      * LayoutParams.
   2145      *
   2146      * @attr ref android.R.styleable#TextView_height
   2147      */
   2148     @android.view.RemotableViewMethod
   2149     public void setHeight(int pixels) {
   2150         mMaximum = mMinimum = pixels;
   2151         mMaxMode = mMinMode = PIXELS;
   2152 
   2153         requestLayout();
   2154         invalidate();
   2155     }
   2156 
   2157     /**
   2158      * Makes the TextView at least this many ems wide
   2159      *
   2160      * @attr ref android.R.styleable#TextView_minEms
   2161      */
   2162     @android.view.RemotableViewMethod
   2163     public void setMinEms(int minems) {
   2164         mMinWidth = minems;
   2165         mMinWidthMode = EMS;
   2166 
   2167         requestLayout();
   2168         invalidate();
   2169     }
   2170 
   2171     /**
   2172      * Makes the TextView at least this many pixels wide
   2173      *
   2174      * @attr ref android.R.styleable#TextView_minWidth
   2175      */
   2176     @android.view.RemotableViewMethod
   2177     public void setMinWidth(int minpixels) {
   2178         mMinWidth = minpixels;
   2179         mMinWidthMode = PIXELS;
   2180 
   2181         requestLayout();
   2182         invalidate();
   2183     }
   2184 
   2185     /**
   2186      * Makes the TextView at most this many ems wide
   2187      *
   2188      * @attr ref android.R.styleable#TextView_maxEms
   2189      */
   2190     @android.view.RemotableViewMethod
   2191     public void setMaxEms(int maxems) {
   2192         mMaxWidth = maxems;
   2193         mMaxWidthMode = EMS;
   2194 
   2195         requestLayout();
   2196         invalidate();
   2197     }
   2198 
   2199     /**
   2200      * Makes the TextView at most this many pixels wide
   2201      *
   2202      * @attr ref android.R.styleable#TextView_maxWidth
   2203      */
   2204     @android.view.RemotableViewMethod
   2205     public void setMaxWidth(int maxpixels) {
   2206         mMaxWidth = maxpixels;
   2207         mMaxWidthMode = PIXELS;
   2208 
   2209         requestLayout();
   2210         invalidate();
   2211     }
   2212 
   2213     /**
   2214      * Makes the TextView exactly this many ems wide
   2215      *
   2216      * @attr ref android.R.styleable#TextView_ems
   2217      */
   2218     @android.view.RemotableViewMethod
   2219     public void setEms(int ems) {
   2220         mMaxWidth = mMinWidth = ems;
   2221         mMaxWidthMode = mMinWidthMode = EMS;
   2222 
   2223         requestLayout();
   2224         invalidate();
   2225     }
   2226 
   2227     /**
   2228      * Makes the TextView exactly this many pixels wide.
   2229      * You could do the same thing by specifying this number in the
   2230      * LayoutParams.
   2231      *
   2232      * @attr ref android.R.styleable#TextView_width
   2233      */
   2234     @android.view.RemotableViewMethod
   2235     public void setWidth(int pixels) {
   2236         mMaxWidth = mMinWidth = pixels;
   2237         mMaxWidthMode = mMinWidthMode = PIXELS;
   2238 
   2239         requestLayout();
   2240         invalidate();
   2241     }
   2242 
   2243 
   2244     /**
   2245      * Sets line spacing for this TextView.  Each line will have its height
   2246      * multiplied by <code>mult</code> and have <code>add</code> added to it.
   2247      *
   2248      * @attr ref android.R.styleable#TextView_lineSpacingExtra
   2249      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
   2250      */
   2251     public void setLineSpacing(float add, float mult) {
   2252         mSpacingMult = mult;
   2253         mSpacingAdd = add;
   2254 
   2255         if (mLayout != null) {
   2256             nullLayouts();
   2257             requestLayout();
   2258             invalidate();
   2259         }
   2260     }
   2261 
   2262     /**
   2263      * Convenience method: Append the specified text to the TextView's
   2264      * display buffer, upgrading it to BufferType.EDITABLE if it was
   2265      * not already editable.
   2266      */
   2267     public final void append(CharSequence text) {
   2268         append(text, 0, text.length());
   2269     }
   2270 
   2271     /**
   2272      * Convenience method: Append the specified text slice to the TextView's
   2273      * display buffer, upgrading it to BufferType.EDITABLE if it was
   2274      * not already editable.
   2275      */
   2276     public void append(CharSequence text, int start, int end) {
   2277         if (!(mText instanceof Editable)) {
   2278             setText(mText, BufferType.EDITABLE);
   2279         }
   2280 
   2281         ((Editable) mText).append(text, start, end);
   2282     }
   2283 
   2284     private void updateTextColors() {
   2285         boolean inval = false;
   2286         int color = mTextColor.getColorForState(getDrawableState(), 0);
   2287         if (color != mCurTextColor) {
   2288             mCurTextColor = color;
   2289             inval = true;
   2290         }
   2291         if (mLinkTextColor != null) {
   2292             color = mLinkTextColor.getColorForState(getDrawableState(), 0);
   2293             if (color != mTextPaint.linkColor) {
   2294                 mTextPaint.linkColor = color;
   2295                 inval = true;
   2296             }
   2297         }
   2298         if (mHintTextColor != null) {
   2299             color = mHintTextColor.getColorForState(getDrawableState(), 0);
   2300             if (color != mCurHintTextColor && mText.length() == 0) {
   2301                 mCurHintTextColor = color;
   2302                 inval = true;
   2303             }
   2304         }
   2305         if (inval) {
   2306             invalidate();
   2307         }
   2308     }
   2309 
   2310     @Override
   2311     protected void drawableStateChanged() {
   2312         super.drawableStateChanged();
   2313         if (mTextColor != null && mTextColor.isStateful()
   2314                 || (mHintTextColor != null && mHintTextColor.isStateful())
   2315                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
   2316             updateTextColors();
   2317         }
   2318 
   2319         final Drawables dr = mDrawables;
   2320         if (dr != null) {
   2321             int[] state = getDrawableState();
   2322             if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
   2323                 dr.mDrawableTop.setState(state);
   2324             }
   2325             if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
   2326                 dr.mDrawableBottom.setState(state);
   2327             }
   2328             if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
   2329                 dr.mDrawableLeft.setState(state);
   2330             }
   2331             if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
   2332                 dr.mDrawableRight.setState(state);
   2333             }
   2334         }
   2335     }
   2336 
   2337     /**
   2338      * User interface state that is stored by TextView for implementing
   2339      * {@link View#onSaveInstanceState}.
   2340      */
   2341     public static class SavedState extends BaseSavedState {
   2342         int selStart;
   2343         int selEnd;
   2344         CharSequence text;
   2345         boolean frozenWithFocus;
   2346         CharSequence error;
   2347 
   2348         SavedState(Parcelable superState) {
   2349             super(superState);
   2350         }
   2351 
   2352         @Override
   2353         public void writeToParcel(Parcel out, int flags) {
   2354             super.writeToParcel(out, flags);
   2355             out.writeInt(selStart);
   2356             out.writeInt(selEnd);
   2357             out.writeInt(frozenWithFocus ? 1 : 0);
   2358             TextUtils.writeToParcel(text, out, flags);
   2359 
   2360             if (error == null) {
   2361                 out.writeInt(0);
   2362             } else {
   2363                 out.writeInt(1);
   2364                 TextUtils.writeToParcel(error, out, flags);
   2365             }
   2366         }
   2367 
   2368         @Override
   2369         public String toString() {
   2370             String str = "TextView.SavedState{"
   2371                     + Integer.toHexString(System.identityHashCode(this))
   2372                     + " start=" + selStart + " end=" + selEnd;
   2373             if (text != null) {
   2374                 str += " text=" + text;
   2375             }
   2376             return str + "}";
   2377         }
   2378 
   2379         @SuppressWarnings("hiding")
   2380         public static final Parcelable.Creator<SavedState> CREATOR
   2381                 = new Parcelable.Creator<SavedState>() {
   2382             public SavedState createFromParcel(Parcel in) {
   2383                 return new SavedState(in);
   2384             }
   2385 
   2386             public SavedState[] newArray(int size) {
   2387                 return new SavedState[size];
   2388             }
   2389         };
   2390 
   2391         private SavedState(Parcel in) {
   2392             super(in);
   2393             selStart = in.readInt();
   2394             selEnd = in.readInt();
   2395             frozenWithFocus = (in.readInt() != 0);
   2396             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   2397 
   2398             if (in.readInt() != 0) {
   2399                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   2400             }
   2401         }
   2402     }
   2403 
   2404     @Override
   2405     public Parcelable onSaveInstanceState() {
   2406         Parcelable superState = super.onSaveInstanceState();
   2407 
   2408         // Save state if we are forced to
   2409         boolean save = mFreezesText;
   2410         int start = 0;
   2411         int end = 0;
   2412 
   2413         if (mText != null) {
   2414             start = getSelectionStart();
   2415             end = getSelectionEnd();
   2416             if (start >= 0 || end >= 0) {
   2417                 // Or save state if there is a selection
   2418                 save = true;
   2419             }
   2420         }
   2421 
   2422         if (save) {
   2423             SavedState ss = new SavedState(superState);
   2424             // XXX Should also save the current scroll position!
   2425             ss.selStart = start;
   2426             ss.selEnd = end;
   2427 
   2428             if (mText instanceof Spanned) {
   2429                 /*
   2430                  * Calling setText() strips off any ChangeWatchers;
   2431                  * strip them now to avoid leaking references.
   2432                  * But do it to a copy so that if there are any
   2433                  * further changes to the text of this view, it
   2434                  * won't get into an inconsistent state.
   2435                  */
   2436 
   2437                 Spannable sp = new SpannableString(mText);
   2438 
   2439                 for (ChangeWatcher cw :
   2440                      sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
   2441                     sp.removeSpan(cw);
   2442                 }
   2443 
   2444                 ss.text = sp;
   2445             } else {
   2446                 ss.text = mText.toString();
   2447             }
   2448 
   2449             if (isFocused() && start >= 0 && end >= 0) {
   2450                 ss.frozenWithFocus = true;
   2451             }
   2452 
   2453             ss.error = mError;
   2454 
   2455             return ss;
   2456         }
   2457 
   2458         return superState;
   2459     }
   2460 
   2461     @Override
   2462     public void onRestoreInstanceState(Parcelable state) {
   2463         if (!(state instanceof SavedState)) {
   2464             super.onRestoreInstanceState(state);
   2465             return;
   2466         }
   2467 
   2468         SavedState ss = (SavedState)state;
   2469         super.onRestoreInstanceState(ss.getSuperState());
   2470 
   2471         // XXX restore buffer type too, as well as lots of other stuff
   2472         if (ss.text != null) {
   2473             setText(ss.text);
   2474         }
   2475 
   2476         if (ss.selStart >= 0 && ss.selEnd >= 0) {
   2477             if (mText instanceof Spannable) {
   2478                 int len = mText.length();
   2479 
   2480                 if (ss.selStart > len || ss.selEnd > len) {
   2481                     String restored = "";
   2482 
   2483                     if (ss.text != null) {
   2484                         restored = "(restored) ";
   2485                     }
   2486 
   2487                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
   2488                           "/" + ss.selEnd + " out of range for " + restored +
   2489                           "text " + mText);
   2490                 } else {
   2491                     Selection.setSelection((Spannable) mText, ss.selStart,
   2492                                            ss.selEnd);
   2493 
   2494                     if (ss.frozenWithFocus) {
   2495                         mFrozenWithFocus = true;
   2496                     }
   2497                 }
   2498             }
   2499         }
   2500 
   2501         if (ss.error != null) {
   2502             final CharSequence error = ss.error;
   2503             // Display the error later, after the first layout pass
   2504             post(new Runnable() {
   2505                 public void run() {
   2506                     setError(error);
   2507                 }
   2508             });
   2509         }
   2510     }
   2511 
   2512     /**
   2513      * Control whether this text view saves its entire text contents when
   2514      * freezing to an icicle, in addition to dynamic state such as cursor
   2515      * position.  By default this is false, not saving the text.  Set to true
   2516      * if the text in the text view is not being saved somewhere else in
   2517      * persistent storage (such as in a content provider) so that if the
   2518      * view is later thawed the user will not lose their data.
   2519      *
   2520      * @param freezesText Controls whether a frozen icicle should include the
   2521      * entire text data: true to include it, false to not.
   2522      *
   2523      * @attr ref android.R.styleable#TextView_freezesText
   2524      */
   2525     @android.view.RemotableViewMethod
   2526     public void setFreezesText(boolean freezesText) {
   2527         mFreezesText = freezesText;
   2528     }
   2529 
   2530     /**
   2531      * Return whether this text view is including its entire text contents
   2532      * in frozen icicles.
   2533      *
   2534      * @return Returns true if text is included, false if it isn't.
   2535      *
   2536      * @see #setFreezesText
   2537      */
   2538     public boolean getFreezesText() {
   2539         return mFreezesText;
   2540     }
   2541 
   2542     ///////////////////////////////////////////////////////////////////////////
   2543 
   2544     /**
   2545      * Sets the Factory used to create new Editables.
   2546      */
   2547     public final void setEditableFactory(Editable.Factory factory) {
   2548         mEditableFactory = factory;
   2549         setText(mText);
   2550     }
   2551 
   2552     /**
   2553      * Sets the Factory used to create new Spannables.
   2554      */
   2555     public final void setSpannableFactory(Spannable.Factory factory) {
   2556         mSpannableFactory = factory;
   2557         setText(mText);
   2558     }
   2559 
   2560     /**
   2561      * Sets the string value of the TextView. TextView <em>does not</em> accept
   2562      * HTML-like formatting, which you can do with text strings in XML resource files.
   2563      * To style your strings, attach android.text.style.* objects to a
   2564      * {@link android.text.SpannableString SpannableString}, or see the
   2565      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
   2566      * Available Resource Types</a> documentation for an example of setting
   2567      * formatted text in the XML resource file.
   2568      *
   2569      * @attr ref android.R.styleable#TextView_text
   2570      */
   2571     @android.view.RemotableViewMethod
   2572     public final void setText(CharSequence text) {
   2573         setText(text, mBufferType);
   2574     }
   2575 
   2576     /**
   2577      * Like {@link #setText(CharSequence)},
   2578      * except that the cursor position (if any) is retained in the new text.
   2579      *
   2580      * @param text The new text to place in the text view.
   2581      *
   2582      * @see #setText(CharSequence)
   2583      */
   2584     @android.view.RemotableViewMethod
   2585     public final void setTextKeepState(CharSequence text) {
   2586         setTextKeepState(text, mBufferType);
   2587     }
   2588 
   2589     /**
   2590      * Sets the text that this TextView is to display (see
   2591      * {@link #setText(CharSequence)}) and also sets whether it is stored
   2592      * in a styleable/spannable buffer and whether it is editable.
   2593      *
   2594      * @attr ref android.R.styleable#TextView_text
   2595      * @attr ref android.R.styleable#TextView_bufferType
   2596      */
   2597     public void setText(CharSequence text, BufferType type) {
   2598         setText(text, type, true, 0);
   2599 
   2600         if (mCharWrapper != null) {
   2601             mCharWrapper.mChars = null;
   2602         }
   2603     }
   2604 
   2605     private void setText(CharSequence text, BufferType type,
   2606                          boolean notifyBefore, int oldlen) {
   2607         if (text == null) {
   2608             text = "";
   2609         }
   2610 
   2611         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
   2612 
   2613         if (text instanceof Spanned &&
   2614             ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
   2615             setHorizontalFadingEdgeEnabled(true);
   2616             setEllipsize(TextUtils.TruncateAt.MARQUEE);
   2617         }
   2618 
   2619         int n = mFilters.length;
   2620         for (int i = 0; i < n; i++) {
   2621             CharSequence out = mFilters[i].filter(text, 0, text.length(),
   2622                                                   EMPTY_SPANNED, 0, 0);
   2623             if (out != null) {
   2624                 text = out;
   2625             }
   2626         }
   2627 
   2628         if (notifyBefore) {
   2629             if (mText != null) {
   2630                 oldlen = mText.length();
   2631                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
   2632             } else {
   2633                 sendBeforeTextChanged("", 0, 0, text.length());
   2634             }
   2635         }
   2636 
   2637         boolean needEditableForNotification = false;
   2638 
   2639         if (mListeners != null && mListeners.size() != 0) {
   2640             needEditableForNotification = true;
   2641         }
   2642 
   2643         if (type == BufferType.EDITABLE || mInput != null ||
   2644             needEditableForNotification) {
   2645             Editable t = mEditableFactory.newEditable(text);
   2646             text = t;
   2647             setFilters(t, mFilters);
   2648             InputMethodManager imm = InputMethodManager.peekInstance();
   2649             if (imm != null) imm.restartInput(this);
   2650         } else if (type == BufferType.SPANNABLE || mMovement != null) {
   2651             text = mSpannableFactory.newSpannable(text);
   2652         } else if (!(text instanceof CharWrapper)) {
   2653             text = TextUtils.stringOrSpannedString(text);
   2654         }
   2655 
   2656         if (mAutoLinkMask != 0) {
   2657             Spannable s2;
   2658 
   2659             if (type == BufferType.EDITABLE || text instanceof Spannable) {
   2660                 s2 = (Spannable) text;
   2661             } else {
   2662                 s2 = mSpannableFactory.newSpannable(text);
   2663             }
   2664 
   2665             if (Linkify.addLinks(s2, mAutoLinkMask)) {
   2666                 text = s2;
   2667                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
   2668 
   2669                 /*
   2670                  * We must go ahead and set the text before changing the
   2671                  * movement method, because setMovementMethod() may call
   2672                  * setText() again to try to upgrade the buffer type.
   2673                  */
   2674                 mText = text;
   2675 
   2676                 if (mLinksClickable) {
   2677                     setMovementMethod(LinkMovementMethod.getInstance());
   2678                 }
   2679             }
   2680         }
   2681 
   2682         mBufferType = type;
   2683         mText = text;
   2684 
   2685         if (mTransformation == null)
   2686             mTransformed = text;
   2687         else
   2688             mTransformed = mTransformation.getTransformation(text, this);
   2689 
   2690         final int textLength = text.length();
   2691 
   2692         if (text instanceof Spannable) {
   2693             Spannable sp = (Spannable) text;
   2694 
   2695             // Remove any ChangeWatchers that might have come
   2696             // from other TextViews.
   2697             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
   2698             final int count = watchers.length;
   2699             for (int i = 0; i < count; i++)
   2700                 sp.removeSpan(watchers[i]);
   2701 
   2702             if (mChangeWatcher == null)
   2703                 mChangeWatcher = new ChangeWatcher();
   2704 
   2705             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
   2706                        (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
   2707 
   2708             if (mInput != null) {
   2709                 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   2710             }
   2711 
   2712             if (mTransformation != null) {
   2713                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   2714 
   2715             }
   2716 
   2717             if (mMovement != null) {
   2718                 mMovement.initialize(this, (Spannable) text);
   2719 
   2720                 /*
   2721                  * Initializing the movement method will have set the
   2722                  * selection, so reset mSelectionMoved to keep that from
   2723                  * interfering with the normal on-focus selection-setting.
   2724                  */
   2725                 mSelectionMoved = false;
   2726             }
   2727         }
   2728 
   2729         if (mLayout != null) {
   2730             checkForRelayout();
   2731         }
   2732 
   2733         sendOnTextChanged(text, 0, oldlen, textLength);
   2734         onTextChanged(text, 0, oldlen, textLength);
   2735 
   2736         if (needEditableForNotification) {
   2737             sendAfterTextChanged((Editable) text);
   2738         }
   2739 
   2740         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
   2741         prepareCursorControllers();
   2742     }
   2743 
   2744     /**
   2745      * Sets the TextView to display the specified slice of the specified
   2746      * char array.  You must promise that you will not change the contents
   2747      * of the array except for right before another call to setText(),
   2748      * since the TextView has no way to know that the text
   2749      * has changed and that it needs to invalidate and re-layout.
   2750      */
   2751     public final void setText(char[] text, int start, int len) {
   2752         int oldlen = 0;
   2753 
   2754         if (start < 0 || len < 0 || start + len > text.length) {
   2755             throw new IndexOutOfBoundsException(start + ", " + len);
   2756         }
   2757 
   2758         /*
   2759          * We must do the before-notification here ourselves because if
   2760          * the old text is a CharWrapper we destroy it before calling
   2761          * into the normal path.
   2762          */
   2763         if (mText != null) {
   2764             oldlen = mText.length();
   2765             sendBeforeTextChanged(mText, 0, oldlen, len);
   2766         } else {
   2767             sendBeforeTextChanged("", 0, 0, len);
   2768         }
   2769 
   2770         if (mCharWrapper == null) {
   2771             mCharWrapper = new CharWrapper(text, start, len);
   2772         } else {
   2773             mCharWrapper.set(text, start, len);
   2774         }
   2775 
   2776         setText(mCharWrapper, mBufferType, false, oldlen);
   2777     }
   2778 
   2779     private static class CharWrapper
   2780             implements CharSequence, GetChars, GraphicsOperations {
   2781         private char[] mChars;
   2782         private int mStart, mLength;
   2783 
   2784         public CharWrapper(char[] chars, int start, int len) {
   2785             mChars = chars;
   2786             mStart = start;
   2787             mLength = len;
   2788         }
   2789 
   2790         /* package */ void set(char[] chars, int start, int len) {
   2791             mChars = chars;
   2792             mStart = start;
   2793             mLength = len;
   2794         }
   2795 
   2796         public int length() {
   2797             return mLength;
   2798         }
   2799 
   2800         public char charAt(int off) {
   2801             return mChars[off + mStart];
   2802         }
   2803 
   2804         @Override
   2805         public String toString() {
   2806             return new String(mChars, mStart, mLength);
   2807         }
   2808 
   2809         public CharSequence subSequence(int start, int end) {
   2810             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   2811                 throw new IndexOutOfBoundsException(start + ", " + end);
   2812             }
   2813 
   2814             return new String(mChars, start + mStart, end - start);
   2815         }
   2816 
   2817         public void getChars(int start, int end, char[] buf, int off) {
   2818             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   2819                 throw new IndexOutOfBoundsException(start + ", " + end);
   2820             }
   2821 
   2822             System.arraycopy(mChars, start + mStart, buf, off, end - start);
   2823         }
   2824 
   2825         public void drawText(Canvas c, int start, int end,
   2826                              float x, float y, Paint p) {
   2827             c.drawText(mChars, start + mStart, end - start, x, y, p);
   2828         }
   2829 
   2830         public float measureText(int start, int end, Paint p) {
   2831             return p.measureText(mChars, start + mStart, end - start);
   2832         }
   2833 
   2834         public int getTextWidths(int start, int end, float[] widths, Paint p) {
   2835             return p.getTextWidths(mChars, start + mStart, end - start, widths);
   2836         }
   2837     }
   2838 
   2839     /**
   2840      * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
   2841      * except that the cursor position (if any) is retained in the new text.
   2842      *
   2843      * @see #setText(CharSequence, android.widget.TextView.BufferType)
   2844      */
   2845     public final void setTextKeepState(CharSequence text, BufferType type) {
   2846         int start = getSelectionStart();
   2847         int end = getSelectionEnd();
   2848         int len = text.length();
   2849 
   2850         setText(text, type);
   2851 
   2852         if (start >= 0 || end >= 0) {
   2853             if (mText instanceof Spannable) {
   2854                 Selection.setSelection((Spannable) mText,
   2855                                        Math.max(0, Math.min(start, len)),
   2856                                        Math.max(0, Math.min(end, len)));
   2857             }
   2858         }
   2859     }
   2860 
   2861     @android.view.RemotableViewMethod
   2862     public final void setText(int resid) {
   2863         setText(getContext().getResources().getText(resid));
   2864     }
   2865 
   2866     public final void setText(int resid, BufferType type) {
   2867         setText(getContext().getResources().getText(resid), type);
   2868     }
   2869 
   2870     /**
   2871      * Sets the text to be displayed when the text of the TextView is empty.
   2872      * Null means to use the normal empty text. The hint does not currently
   2873      * participate in determining the size of the view.
   2874      *
   2875      * @attr ref android.R.styleable#TextView_hint
   2876      */
   2877     @android.view.RemotableViewMethod
   2878     public final void setHint(CharSequence hint) {
   2879         mHint = TextUtils.stringOrSpannedString(hint);
   2880 
   2881         if (mLayout != null) {
   2882             checkForRelayout();
   2883         }
   2884 
   2885         if (mText.length() == 0) {
   2886             invalidate();
   2887         }
   2888     }
   2889 
   2890     /**
   2891      * Sets the text to be displayed when the text of the TextView is empty,
   2892      * from a resource.
   2893      *
   2894      * @attr ref android.R.styleable#TextView_hint
   2895      */
   2896     @android.view.RemotableViewMethod
   2897     public final void setHint(int resid) {
   2898         setHint(getContext().getResources().getText(resid));
   2899     }
   2900 
   2901     /**
   2902      * Returns the hint that is displayed when the text of the TextView
   2903      * is empty.
   2904      *
   2905      * @attr ref android.R.styleable#TextView_hint
   2906      */
   2907     @ViewDebug.CapturedViewProperty
   2908     public CharSequence getHint() {
   2909         return mHint;
   2910     }
   2911 
   2912     /**
   2913      * Set the type of the content with a constant as defined for
   2914      * {@link EditorInfo#inputType}.  This will take care of changing
   2915      * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
   2916      * match the given content type.  If the given content type is
   2917      * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
   2918      * not be displayed for this text view.
   2919      *
   2920      * @see #getInputType()
   2921      * @see #setRawInputType(int)
   2922      * @see android.text.InputType
   2923      * @attr ref android.R.styleable#TextView_inputType
   2924      */
   2925     public void setInputType(int type) {
   2926         final boolean wasPassword = isPasswordInputType(mInputType);
   2927         final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
   2928         setInputType(type, false);
   2929         final boolean isPassword = isPasswordInputType(type);
   2930         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
   2931         boolean forceUpdate = false;
   2932         if (isPassword) {
   2933             setTransformationMethod(PasswordTransformationMethod.getInstance());
   2934             setTypefaceByIndex(MONOSPACE, 0);
   2935         } else if (isVisiblePassword) {
   2936             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   2937                 forceUpdate = true;
   2938             }
   2939             setTypefaceByIndex(MONOSPACE, 0);
   2940         } else if (wasPassword || wasVisiblePassword) {
   2941             // not in password mode, clean up typeface and transformation
   2942             setTypefaceByIndex(-1, -1);
   2943             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   2944                 forceUpdate = true;
   2945             }
   2946         }
   2947 
   2948         boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
   2949                         | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
   2950                 (EditorInfo.TYPE_CLASS_TEXT
   2951                         | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
   2952 
   2953         // We need to update the single line mode if it has changed or we
   2954         // were previously in password mode.
   2955         if (mSingleLine == multiLine || forceUpdate) {
   2956             // Change single line mode, but only change the transformation if
   2957             // we are not in password mode.
   2958             applySingleLine(!multiLine, !isPassword);
   2959         }
   2960 
   2961         InputMethodManager imm = InputMethodManager.peekInstance();
   2962         if (imm != null) imm.restartInput(this);
   2963     }
   2964 
   2965     /**
   2966      * It would be better to rely on the input type for everything. A password inputType should have
   2967      * a password transformation. We should hence use isPasswordInputType instead of this method.
   2968      *
   2969      * We should:
   2970      * - Call setInputType in setKeyListener instead of changing the input type directly (which
   2971      * would install the correct transformation).
   2972      * - Refuse the installation of a non-password transformation in setTransformation if the input
   2973      * type is password.
   2974      *
   2975      * However, this is like this for legacy reasons and we cannot break existing apps. This method
   2976      * is useful since it matches what the user can see (obfuscated text or not).
   2977      *
   2978      * @return true if the current transformation method is of the password type.
   2979      */
   2980     private boolean hasPasswordTransformationMethod() {
   2981         return mTransformation instanceof PasswordTransformationMethod;
   2982     }
   2983 
   2984     private boolean isPasswordInputType(int inputType) {
   2985         final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
   2986                 | EditorInfo.TYPE_MASK_VARIATION);
   2987         return variation
   2988                 == (EditorInfo.TYPE_CLASS_TEXT
   2989                         | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
   2990     }
   2991 
   2992     private boolean isVisiblePasswordInputType(int inputType) {
   2993         final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
   2994                 | EditorInfo.TYPE_MASK_VARIATION);
   2995         return variation
   2996                 == (EditorInfo.TYPE_CLASS_TEXT
   2997                         | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
   2998     }
   2999 
   3000     /**
   3001      * Directly change the content type integer of the text view, without
   3002      * modifying any other state.
   3003      * @see #setInputType(int)
   3004      * @see android.text.InputType
   3005      * @attr ref android.R.styleable#TextView_inputType
   3006      */
   3007     public void setRawInputType(int type) {
   3008         mInputType = type;
   3009     }
   3010 
   3011     private void setInputType(int type, boolean direct) {
   3012         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
   3013         KeyListener input;
   3014         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
   3015             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
   3016             TextKeyListener.Capitalize cap;
   3017             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
   3018                 cap = TextKeyListener.Capitalize.CHARACTERS;
   3019             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
   3020                 cap = TextKeyListener.Capitalize.WORDS;
   3021             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
   3022                 cap = TextKeyListener.Capitalize.SENTENCES;
   3023             } else {
   3024                 cap = TextKeyListener.Capitalize.NONE;
   3025             }
   3026             input = TextKeyListener.getInstance(autotext, cap);
   3027         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
   3028             input = DigitsKeyListener.getInstance(
   3029                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
   3030                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
   3031         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
   3032             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
   3033                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
   3034                     input = DateKeyListener.getInstance();
   3035                     break;
   3036                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
   3037                     input = TimeKeyListener.getInstance();
   3038                     break;
   3039                 default:
   3040                     input = DateTimeKeyListener.getInstance();
   3041                     break;
   3042             }
   3043         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
   3044             input = DialerKeyListener.getInstance();
   3045         } else {
   3046             input = TextKeyListener.getInstance();
   3047         }
   3048         setRawInputType(type);
   3049         if (direct) mInput = input;
   3050         else {
   3051             setKeyListenerOnly(input);
   3052         }
   3053     }
   3054 
   3055     /**
   3056      * Get the type of the content.
   3057      *
   3058      * @see #setInputType(int)
   3059      * @see android.text.InputType
   3060      */
   3061     public int getInputType() {
   3062         return mInputType;
   3063     }
   3064 
   3065     /**
   3066      * Change the editor type integer associated with the text view, which
   3067      * will be reported to an IME with {@link EditorInfo#imeOptions} when it
   3068      * has focus.
   3069      * @see #getImeOptions
   3070      * @see android.view.inputmethod.EditorInfo
   3071      * @attr ref android.R.styleable#TextView_imeOptions
   3072      */
   3073     public void setImeOptions(int imeOptions) {
   3074         if (mInputContentType == null) {
   3075             mInputContentType = new InputContentType();
   3076         }
   3077         mInputContentType.imeOptions = imeOptions;
   3078     }
   3079 
   3080     /**
   3081      * Get the type of the IME editor.
   3082      *
   3083      * @see #setImeOptions(int)
   3084      * @see android.view.inputmethod.EditorInfo
   3085      */
   3086     public int getImeOptions() {
   3087         return mInputContentType != null
   3088                 ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
   3089     }
   3090 
   3091     /**
   3092      * Change the custom IME action associated with the text view, which
   3093      * will be reported to an IME with {@link EditorInfo#actionLabel}
   3094      * and {@link EditorInfo#actionId} when it has focus.
   3095      * @see #getImeActionLabel
   3096      * @see #getImeActionId
   3097      * @see android.view.inputmethod.EditorInfo
   3098      * @attr ref android.R.styleable#TextView_imeActionLabel
   3099      * @attr ref android.R.styleable#TextView_imeActionId
   3100      */
   3101     public void setImeActionLabel(CharSequence label, int actionId) {
   3102         if (mInputContentType == null) {
   3103             mInputContentType = new InputContentType();
   3104         }
   3105         mInputContentType.imeActionLabel = label;
   3106         mInputContentType.imeActionId = actionId;
   3107     }
   3108 
   3109     /**
   3110      * Get the IME action label previous set with {@link #setImeActionLabel}.
   3111      *
   3112      * @see #setImeActionLabel
   3113      * @see android.view.inputmethod.EditorInfo
   3114      */
   3115     public CharSequence getImeActionLabel() {
   3116         return mInputContentType != null
   3117                 ? mInputContentType.imeActionLabel : null;
   3118     }
   3119 
   3120     /**
   3121      * Get the IME action ID previous set with {@link #setImeActionLabel}.
   3122      *
   3123      * @see #setImeActionLabel
   3124      * @see android.view.inputmethod.EditorInfo
   3125      */
   3126     public int getImeActionId() {
   3127         return mInputContentType != null
   3128                 ? mInputContentType.imeActionId : 0;
   3129     }
   3130 
   3131     /**
   3132      * Set a special listener to be called when an action is performed
   3133      * on the text view.  This will be called when the enter key is pressed,
   3134      * or when an action supplied to the IME is selected by the user.  Setting
   3135      * this means that the normal hard key event will not insert a newline
   3136      * into the text view, even if it is multi-line; holding down the ALT
   3137      * modifier will, however, allow the user to insert a newline character.
   3138      */
   3139     public void setOnEditorActionListener(OnEditorActionListener l) {
   3140         if (mInputContentType == null) {
   3141             mInputContentType = new InputContentType();
   3142         }
   3143         mInputContentType.onEditorActionListener = l;
   3144     }
   3145 
   3146     /**
   3147      * Called when an attached input method calls
   3148      * {@link InputConnection#performEditorAction(int)
   3149      * InputConnection.performEditorAction()}
   3150      * for this text view.  The default implementation will call your action
   3151      * listener supplied to {@link #setOnEditorActionListener}, or perform
   3152      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
   3153      * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
   3154      * EditorInfo.IME_ACTION_DONE}.
   3155      *
   3156      * <p>For backwards compatibility, if no IME options have been set and the
   3157      * text view would not normally advance focus on enter, then
   3158      * the NEXT and DONE actions received here will be turned into an enter
   3159      * key down/up pair to go through the normal key handling.
   3160      *
   3161      * @param actionCode The code of the action being performed.
   3162      *
   3163      * @see #setOnEditorActionListener
   3164      */
   3165     public void onEditorAction(int actionCode) {
   3166         final InputContentType ict = mInputContentType;
   3167         if (ict != null) {
   3168             if (ict.onEditorActionListener != null) {
   3169                 if (ict.onEditorActionListener.onEditorAction(this,
   3170                         actionCode, null)) {
   3171                     return;
   3172                 }
   3173             }
   3174 
   3175             // This is the handling for some default action.
   3176             // Note that for backwards compatibility we don't do this
   3177             // default handling if explicit ime options have not been given,
   3178             // instead turning this into the normal enter key codes that an
   3179             // app may be expecting.
   3180             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
   3181                 View v = focusSearch(FOCUS_DOWN);
   3182                 if (v != null) {
   3183                     if (!v.requestFocus(FOCUS_DOWN)) {
   3184                         throw new IllegalStateException("focus search returned a view " +
   3185                                 "that wasn't able to take focus!");
   3186                     }
   3187                 }
   3188                 return;
   3189 
   3190             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
   3191                 InputMethodManager imm = InputMethodManager.peekInstance();
   3192                 if (imm != null) {
   3193                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   3194                 }
   3195                 return;
   3196             }
   3197         }
   3198 
   3199         Handler h = getHandler();
   3200         if (h != null) {
   3201             long eventTime = SystemClock.uptimeMillis();
   3202             h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
   3203                     new KeyEvent(eventTime, eventTime,
   3204                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
   3205                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   3206                     | KeyEvent.FLAG_EDITOR_ACTION)));
   3207             h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
   3208                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
   3209                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
   3210                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   3211                     | KeyEvent.FLAG_EDITOR_ACTION)));
   3212         }
   3213     }
   3214 
   3215     /**
   3216      * Set the private content type of the text, which is the
   3217      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
   3218      * field that will be filled in when creating an input connection.
   3219      *
   3220      * @see #getPrivateImeOptions()
   3221      * @see EditorInfo#privateImeOptions
   3222      * @attr ref android.R.styleable#TextView_privateImeOptions
   3223      */
   3224     public void setPrivateImeOptions(String type) {
   3225         if (mInputContentType == null) mInputContentType = new InputContentType();
   3226         mInputContentType.privateImeOptions = type;
   3227     }
   3228 
   3229     /**
   3230      * Get the private type of the content.
   3231      *
   3232      * @see #setPrivateImeOptions(String)
   3233      * @see EditorInfo#privateImeOptions
   3234      */
   3235     public String getPrivateImeOptions() {
   3236         return mInputContentType != null
   3237                 ? mInputContentType.privateImeOptions : null;
   3238     }
   3239 
   3240     /**
   3241      * Set the extra input data of the text, which is the
   3242      * {@link EditorInfo#extras TextBoxAttribute.extras}
   3243      * Bundle that will be filled in when creating an input connection.  The
   3244      * given integer is the resource ID of an XML resource holding an
   3245      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
   3246      *
   3247      * @see #getInputExtras(boolean)
   3248      * @see EditorInfo#extras
   3249      * @attr ref android.R.styleable#TextView_editorExtras
   3250      */
   3251     public void setInputExtras(int xmlResId)
   3252             throws XmlPullParserException, IOException {
   3253         XmlResourceParser parser = getResources().getXml(xmlResId);
   3254         if (mInputContentType == null) mInputContentType = new InputContentType();
   3255         mInputContentType.extras = new Bundle();
   3256         getResources().parseBundleExtras(parser, mInputContentType.extras);
   3257     }
   3258 
   3259     /**
   3260      * Retrieve the input extras currently associated with the text view, which
   3261      * can be viewed as well as modified.
   3262      *
   3263      * @param create If true, the extras will be created if they don't already
   3264      * exist.  Otherwise, null will be returned if none have been created.
   3265      * @see #setInputExtras(int)
   3266      * @see EditorInfo#extras
   3267      * @attr ref android.R.styleable#TextView_editorExtras
   3268      */
   3269     public Bundle getInputExtras(boolean create) {
   3270         if (mInputContentType == null) {
   3271             if (!create) return null;
   3272             mInputContentType = new InputContentType();
   3273         }
   3274         if (mInputContentType.extras == null) {
   3275             if (!create) return null;
   3276             mInputContentType.extras = new Bundle();
   3277         }
   3278         return mInputContentType.extras;
   3279     }
   3280 
   3281     /**
   3282      * Returns the error message that was set to be displayed with
   3283      * {@link #setError}, or <code>null</code> if no error was set
   3284      * or if it the error was cleared by the widget after user input.
   3285      */
   3286     public CharSequence getError() {
   3287         return mError;
   3288     }
   3289 
   3290     /**
   3291      * Sets the right-hand compound drawable of the TextView to the "error"
   3292      * icon and sets an error message that will be displayed in a popup when
   3293      * the TextView has focus.  The icon and error message will be reset to
   3294      * null when any key events cause changes to the TextView's text.  If the
   3295      * <code>error</code> is <code>null</code>, the error message and icon
   3296      * will be cleared.
   3297      */
   3298     @android.view.RemotableViewMethod
   3299     public void setError(CharSequence error) {
   3300         if (error == null) {
   3301             setError(null, null);
   3302         } else {
   3303             Drawable dr = getContext().getResources().
   3304                 getDrawable(com.android.internal.R.drawable.
   3305                             indicator_input_error);
   3306 
   3307             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
   3308             setError(error, dr);
   3309         }
   3310     }
   3311 
   3312     /**
   3313      * Sets the right-hand compound drawable of the TextView to the specified
   3314      * icon and sets an error message that will be displayed in a popup when
   3315      * the TextView has focus.  The icon and error message will be reset to
   3316      * null when any key events cause changes to the TextView's text.  The
   3317      * drawable must already have had {@link Drawable#setBounds} set on it.
   3318      * If the <code>error</code> is <code>null</code>, the error message will
   3319      * be cleared (and you should provide a <code>null</code> icon as well).
   3320      */
   3321     public void setError(CharSequence error, Drawable icon) {
   3322         error = TextUtils.stringOrSpannedString(error);
   3323 
   3324         mError = error;
   3325         mErrorWasChanged = true;
   3326         final Drawables dr = mDrawables;
   3327         if (dr != null) {
   3328             setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
   3329                                  icon, dr.mDrawableBottom);
   3330         } else {
   3331             setCompoundDrawables(null, null, icon, null);
   3332         }
   3333 
   3334         if (error == null) {
   3335             if (mPopup != null) {
   3336                 if (mPopup.isShowing()) {
   3337                     mPopup.dismiss();
   3338                 }
   3339 
   3340                 mPopup = null;
   3341             }
   3342         } else {
   3343             if (isFocused()) {
   3344                 showError();
   3345             }
   3346         }
   3347     }
   3348 
   3349     private void showError() {
   3350         if (getWindowToken() == null) {
   3351             mShowErrorAfterAttach = true;
   3352             return;
   3353         }
   3354 
   3355         if (mPopup == null) {
   3356             LayoutInflater inflater = LayoutInflater.from(getContext());
   3357             final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
   3358                     null);
   3359 
   3360             final float scale = getResources().getDisplayMetrics().density;
   3361             mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f),
   3362                     (int) (50 * scale + 0.5f));
   3363             mPopup.setFocusable(false);
   3364             // The user is entering text, so the input method is needed.  We
   3365             // don't want the popup to be displayed on top of it.
   3366             mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
   3367         }
   3368 
   3369         TextView tv = (TextView) mPopup.getContentView();
   3370         chooseSize(mPopup, mError, tv);
   3371         tv.setText(mError);
   3372 
   3373         mPopup.showAsDropDown(this, getErrorX(), getErrorY());
   3374         mPopup.fixDirection(mPopup.isAboveAnchor());
   3375     }
   3376 
   3377     private static class ErrorPopup extends PopupWindow {
   3378         private boolean mAbove = false;
   3379         private final TextView mView;
   3380 
   3381         ErrorPopup(TextView v, int width, int height) {
   3382             super(v, width, height);
   3383             mView = v;
   3384         }
   3385 
   3386         void fixDirection(boolean above) {
   3387             mAbove = above;
   3388 
   3389             if (above) {
   3390                 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
   3391             } else {
   3392                 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
   3393             }
   3394         }
   3395 
   3396         @Override
   3397         public void update(int x, int y, int w, int h, boolean force) {
   3398             super.update(x, y, w, h, force);
   3399 
   3400             boolean above = isAboveAnchor();
   3401             if (above != mAbove) {
   3402                 fixDirection(above);
   3403             }
   3404         }
   3405     }
   3406 
   3407     /**
   3408      * Returns the Y offset to make the pointy top of the error point
   3409      * at the middle of the error icon.
   3410      */
   3411     private int getErrorX() {
   3412         /*
   3413          * The "25" is the distance between the point and the right edge
   3414          * of the background
   3415          */
   3416         final float scale = getResources().getDisplayMetrics().density;
   3417 
   3418         final Drawables dr = mDrawables;
   3419         return getWidth() - mPopup.getWidth()
   3420                 - getPaddingRight()
   3421                 - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
   3422     }
   3423 
   3424     /**
   3425      * Returns the Y offset to make the pointy top of the error point
   3426      * at the bottom of the error icon.
   3427      */
   3428     private int getErrorY() {
   3429         /*
   3430          * Compound, not extended, because the icon is not clipped
   3431          * if the text height is smaller.
   3432          */
   3433         int vspace = mBottom - mTop -
   3434                      getCompoundPaddingBottom() - getCompoundPaddingTop();
   3435 
   3436         final Drawables dr = mDrawables;
   3437         int icontop = getCompoundPaddingTop()
   3438                 + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
   3439 
   3440         /*
   3441          * The "2" is the distance between the point and the top edge
   3442          * of the background.
   3443          */
   3444 
   3445         return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
   3446                 - getHeight() - 2;
   3447     }
   3448 
   3449     private void hideError() {
   3450         if (mPopup != null) {
   3451             if (mPopup.isShowing()) {
   3452                 mPopup.dismiss();
   3453             }
   3454         }
   3455 
   3456         mShowErrorAfterAttach = false;
   3457     }
   3458 
   3459     private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
   3460         int wid = tv.getPaddingLeft() + tv.getPaddingRight();
   3461         int ht = tv.getPaddingTop() + tv.getPaddingBottom();
   3462 
   3463         /*
   3464          * Figure out how big the text would be if we laid it out to the
   3465          * full width of this view minus the border.
   3466          */
   3467         int cap = getWidth() - wid;
   3468         if (cap < 0) {
   3469             cap = 200; // We must not be measured yet -- setFrame() will fix it.
   3470         }
   3471 
   3472         Layout l = new StaticLayout(text, tv.getPaint(), cap,
   3473                                     Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
   3474         float max = 0;
   3475         for (int i = 0; i < l.getLineCount(); i++) {
   3476             max = Math.max(max, l.getLineWidth(i));
   3477         }
   3478 
   3479         /*
   3480          * Now set the popup size to be big enough for the text plus the border.
   3481          */
   3482         pop.setWidth(wid + (int) Math.ceil(max));
   3483         pop.setHeight(ht + l.getHeight());
   3484     }
   3485 
   3486 
   3487     @Override
   3488     protected boolean setFrame(int l, int t, int r, int b) {
   3489         boolean result = super.setFrame(l, t, r, b);
   3490 
   3491         if (mPopup != null) {
   3492             TextView tv = (TextView) mPopup.getContentView();
   3493             chooseSize(mPopup, mError, tv);
   3494             mPopup.update(this, getErrorX(), getErrorY(),
   3495                           mPopup.getWidth(), mPopup.getHeight());
   3496         }
   3497 
   3498         restartMarqueeIfNeeded();
   3499 
   3500         return result;
   3501     }
   3502 
   3503     private void restartMarqueeIfNeeded() {
   3504         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   3505             mRestartMarquee = false;
   3506             startMarquee();
   3507         }
   3508     }
   3509 
   3510     /**
   3511      * Sets the list of input filters that will be used if the buffer is
   3512      * Editable.  Has no effect otherwise.
   3513      *
   3514      * @attr ref android.R.styleable#TextView_maxLength
   3515      */
   3516     public void setFilters(InputFilter[] filters) {
   3517         if (filters == null) {
   3518             throw new IllegalArgumentException();
   3519         }
   3520 
   3521         mFilters = filters;
   3522 
   3523         if (mText instanceof Editable) {
   3524             setFilters((Editable) mText, filters);
   3525         }
   3526     }
   3527 
   3528     /**
   3529      * Sets the list of input filters on the specified Editable,
   3530      * and includes mInput in the list if it is an InputFilter.
   3531      */
   3532     private void setFilters(Editable e, InputFilter[] filters) {
   3533         if (mInput instanceof InputFilter) {
   3534             InputFilter[] nf = new InputFilter[filters.length + 1];
   3535 
   3536             System.arraycopy(filters, 0, nf, 0, filters.length);
   3537             nf[filters.length] = (InputFilter) mInput;
   3538 
   3539             e.setFilters(nf);
   3540         } else {
   3541             e.setFilters(filters);
   3542         }
   3543     }
   3544 
   3545     /**
   3546      * Returns the current list of input filters.
   3547      */
   3548     public InputFilter[] getFilters() {
   3549         return mFilters;
   3550     }
   3551 
   3552     /////////////////////////////////////////////////////////////////////////
   3553 
   3554     private int getVerticalOffset(boolean forceNormal) {
   3555         int voffset = 0;
   3556         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   3557 
   3558         Layout l = mLayout;
   3559         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   3560             l = mHintLayout;
   3561         }
   3562 
   3563         if (gravity != Gravity.TOP) {
   3564             int boxht;
   3565 
   3566             if (l == mHintLayout) {
   3567                 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
   3568                         getCompoundPaddingBottom();
   3569             } else {
   3570                 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
   3571                         getExtendedPaddingBottom();
   3572             }
   3573             int textht = l.getHeight();
   3574 
   3575             if (textht < boxht) {
   3576                 if (gravity == Gravity.BOTTOM)
   3577                     voffset = boxht - textht;
   3578                 else // (gravity == Gravity.CENTER_VERTICAL)
   3579                     voffset = (boxht - textht) >> 1;
   3580             }
   3581         }
   3582         return voffset;
   3583     }
   3584 
   3585     private int getBottomVerticalOffset(boolean forceNormal) {
   3586         int voffset = 0;
   3587         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   3588 
   3589         Layout l = mLayout;
   3590         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   3591             l = mHintLayout;
   3592         }
   3593 
   3594         if (gravity != Gravity.BOTTOM) {
   3595             int boxht;
   3596 
   3597             if (l == mHintLayout) {
   3598                 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
   3599                         getCompoundPaddingBottom();
   3600             } else {
   3601                 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
   3602                         getExtendedPaddingBottom();
   3603             }
   3604             int textht = l.getHeight();
   3605 
   3606             if (textht < boxht) {
   3607                 if (gravity == Gravity.TOP)
   3608                     voffset = boxht - textht;
   3609                 else // (gravity == Gravity.CENTER_VERTICAL)
   3610                     voffset = (boxht - textht) >> 1;
   3611             }
   3612         }
   3613         return voffset;
   3614     }
   3615 
   3616     private void invalidateCursorPath() {
   3617         if (mHighlightPathBogus) {
   3618             invalidateCursor();
   3619         } else {
   3620             synchronized (sTempRect) {
   3621                 /*
   3622                  * The reason for this concern about the thickness of the
   3623                  * cursor and doing the floor/ceil on the coordinates is that
   3624                  * some EditTexts (notably textfields in the Browser) have
   3625                  * anti-aliased text where not all the characters are
   3626                  * necessarily at integer-multiple locations.  This should
   3627                  * make sure the entire cursor gets invalidated instead of
   3628                  * sometimes missing half a pixel.
   3629                  */
   3630 
   3631                 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
   3632                 if (thick < 1.0f) {
   3633                     thick = 1.0f;
   3634                 }
   3635 
   3636                 thick /= 2;
   3637 
   3638                 mHighlightPath.computeBounds(sTempRect, false);
   3639 
   3640                 int left = getCompoundPaddingLeft();
   3641                 int top = getExtendedPaddingTop() + getVerticalOffset(true);
   3642 
   3643                 invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
   3644                            (int) FloatMath.floor(top + sTempRect.top - thick),
   3645                            (int) FloatMath.ceil(left + sTempRect.right + thick),
   3646                            (int) FloatMath.ceil(top + sTempRect.bottom + thick));
   3647             }
   3648         }
   3649     }
   3650 
   3651     private void invalidateCursor() {
   3652         int where = getSelectionEnd();
   3653 
   3654         invalidateCursor(where, where, where);
   3655     }
   3656 
   3657     private void invalidateCursor(int a, int b, int c) {
   3658         if (mLayout == null) {
   3659             invalidate();
   3660         } else {
   3661             if (a >= 0 || b >= 0 || c >= 0) {
   3662                 int first = Math.min(Math.min(a, b), c);
   3663                 int last = Math.max(Math.max(a, b), c);
   3664 
   3665                 int line = mLayout.getLineForOffset(first);
   3666                 int top = mLayout.getLineTop(line);
   3667 
   3668                 // This is ridiculous, but the descent from the line above
   3669                 // can hang down into the line we really want to redraw,
   3670                 // so we have to invalidate part of the line above to make
   3671                 // sure everything that needs to be redrawn really is.
   3672                 // (But not the whole line above, because that would cause
   3673                 // the same problem with the descenders on the line above it!)
   3674                 if (line > 0) {
   3675                     top -= mLayout.getLineDescent(line - 1);
   3676                 }
   3677 
   3678                 int line2;
   3679 
   3680                 if (first == last)
   3681                     line2 = line;
   3682                 else
   3683                     line2 = mLayout.getLineForOffset(last);
   3684 
   3685                 int bottom = mLayout.getLineTop(line2 + 1);
   3686                 int voffset = getVerticalOffset(true);
   3687 
   3688                 int left = getCompoundPaddingLeft() + mScrollX;
   3689                 invalidate(left, top + voffset + getExtendedPaddingTop(),
   3690                            left + getWidth() - getCompoundPaddingLeft() -
   3691                            getCompoundPaddingRight(),
   3692                            bottom + voffset + getExtendedPaddingTop());
   3693             }
   3694         }
   3695     }
   3696 
   3697     private void registerForPreDraw() {
   3698         final ViewTreeObserver observer = getViewTreeObserver();
   3699         if (observer == null) {
   3700             return;
   3701         }
   3702 
   3703         if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
   3704             observer.addOnPreDrawListener(this);
   3705             mPreDrawState = PREDRAW_PENDING;
   3706         } else if (mPreDrawState == PREDRAW_DONE) {
   3707             mPreDrawState = PREDRAW_PENDING;
   3708         }
   3709 
   3710         // else state is PREDRAW_PENDING, so keep waiting.
   3711     }
   3712 
   3713     /**
   3714      * {@inheritDoc}
   3715      */
   3716     public boolean onPreDraw() {
   3717         if (mPreDrawState != PREDRAW_PENDING) {
   3718             return true;
   3719         }
   3720 
   3721         if (mLayout == null) {
   3722             assumeLayout();
   3723         }
   3724 
   3725         boolean changed = false;
   3726 
   3727         SelectionModifierCursorController selectionController = null;
   3728         if (mSelectionModifierCursorController != null) {
   3729             selectionController = (SelectionModifierCursorController)
   3730                 mSelectionModifierCursorController;
   3731         }
   3732 
   3733 
   3734         if (mMovement != null) {
   3735             /* This code also provides auto-scrolling when a cursor is moved using a
   3736              * CursorController (insertion point or selection limits).
   3737              * For selection, ensure start or end is visible depending on controller's state.
   3738              */
   3739             int curs = getSelectionEnd();
   3740             if (selectionController != null && selectionController.isSelectionStartDragged()) {
   3741                 curs = getSelectionStart();
   3742             }
   3743 
   3744             /*
   3745              * TODO: This should really only keep the end in view if
   3746              * it already was before the text changed.  I'm not sure
   3747              * of a good way to tell from here if it was.
   3748              */
   3749             if (curs < 0 &&
   3750                   (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   3751                 curs = mText.length();
   3752             }
   3753 
   3754             if (curs >= 0) {
   3755                 changed = bringPointIntoView(curs);
   3756             }
   3757         } else {
   3758             changed = bringTextIntoView();
   3759         }
   3760 
   3761         // This has to be checked here since:
   3762         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
   3763         //   a screen rotation) since layout is not yet initialized at that point.
   3764         // - ExtractEditText does not call onFocus when it is displayed. Fixing this issue would
   3765         //   allow to test for hasSelection in onFocusChanged, which would trigger a
   3766         //   startTextSelectionMode here. TODO
   3767         if (mCreatedWithASelection ||
   3768            (this instanceof ExtractEditText && selectionController != null && hasSelection())) {
   3769             startTextSelectionMode();
   3770             mCreatedWithASelection = false;
   3771         }
   3772 
   3773         mPreDrawState = PREDRAW_DONE;
   3774         return !changed;
   3775     }
   3776 
   3777     @Override
   3778     protected void onAttachedToWindow() {
   3779         super.onAttachedToWindow();
   3780 
   3781         mTemporaryDetach = false;
   3782 
   3783         if (mShowErrorAfterAttach) {
   3784             showError();
   3785             mShowErrorAfterAttach = false;
   3786         }
   3787 
   3788         final ViewTreeObserver observer = getViewTreeObserver();
   3789         if (observer != null) {
   3790             if (mInsertionPointCursorController != null) {
   3791                 observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
   3792             }
   3793             if (mSelectionModifierCursorController != null) {
   3794                 observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
   3795             }
   3796         }
   3797     }
   3798 
   3799     @Override
   3800     protected void onDetachedFromWindow() {
   3801         super.onDetachedFromWindow();
   3802 
   3803         final ViewTreeObserver observer = getViewTreeObserver();
   3804         if (observer != null) {
   3805             if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
   3806                 observer.removeOnPreDrawListener(this);
   3807                 mPreDrawState = PREDRAW_NOT_REGISTERED;
   3808             }
   3809             if (mInsertionPointCursorController != null) {
   3810                 observer.removeOnTouchModeChangeListener(mInsertionPointCursorController);
   3811             }
   3812             if (mSelectionModifierCursorController != null) {
   3813                 observer.removeOnTouchModeChangeListener(mSelectionModifierCursorController);
   3814             }
   3815         }
   3816 
   3817         if (mError != null) {
   3818             hideError();
   3819         }
   3820 
   3821         if (mBlink != null) {
   3822             mBlink.cancel();
   3823         }
   3824 
   3825         if (mInsertionPointCursorController != null) {
   3826             mInsertionPointCursorController.onDetached();
   3827         }
   3828 
   3829         if (mSelectionModifierCursorController != null) {
   3830             mSelectionModifierCursorController.onDetached();
   3831         }
   3832 
   3833         hideControllers();
   3834     }
   3835 
   3836     @Override
   3837     protected boolean isPaddingOffsetRequired() {
   3838         return mShadowRadius != 0 || mDrawables != null;
   3839     }
   3840 
   3841     @Override
   3842     protected int getLeftPaddingOffset() {
   3843         return getCompoundPaddingLeft() - mPaddingLeft +
   3844                 (int) Math.min(0, mShadowDx - mShadowRadius);
   3845     }
   3846 
   3847     @Override
   3848     protected int getTopPaddingOffset() {
   3849         return (int) Math.min(0, mShadowDy - mShadowRadius);
   3850     }
   3851 
   3852     @Override
   3853     protected int getBottomPaddingOffset() {
   3854         return (int) Math.max(0, mShadowDy + mShadowRadius);
   3855     }
   3856 
   3857     @Override
   3858     protected int getRightPaddingOffset() {
   3859         return -(getCompoundPaddingRight() - mPaddingRight) +
   3860                 (int) Math.max(0, mShadowDx + mShadowRadius);
   3861     }
   3862 
   3863     @Override
   3864     protected boolean verifyDrawable(Drawable who) {
   3865         final boolean verified = super.verifyDrawable(who);
   3866         if (!verified && mDrawables != null) {
   3867             return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
   3868                     who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
   3869         }
   3870         return verified;
   3871     }
   3872 
   3873     @Override
   3874     public void invalidateDrawable(Drawable drawable) {
   3875         if (verifyDrawable(drawable)) {
   3876             final Rect dirty = drawable.getBounds();
   3877             int scrollX = mScrollX;
   3878             int scrollY = mScrollY;
   3879 
   3880             // IMPORTANT: The coordinates below are based on the coordinates computed
   3881             // for each compound drawable in onDraw(). Make sure to update each section
   3882             // accordingly.
   3883             final TextView.Drawables drawables = mDrawables;
   3884             if (drawables != null) {
   3885                 if (drawable == drawables.mDrawableLeft) {
   3886                     final int compoundPaddingTop = getCompoundPaddingTop();
   3887                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   3888                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   3889 
   3890                     scrollX += mPaddingLeft;
   3891                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
   3892                 } else if (drawable == drawables.mDrawableRight) {
   3893                     final int compoundPaddingTop = getCompoundPaddingTop();
   3894                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   3895                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   3896 
   3897                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
   3898                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
   3899                 } else if (drawable == drawables.mDrawableTop) {
   3900                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   3901                     final int compoundPaddingRight = getCompoundPaddingRight();
   3902                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   3903 
   3904                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
   3905                     scrollY += mPaddingTop;
   3906                 } else if (drawable == drawables.mDrawableBottom) {
   3907                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   3908                     final int compoundPaddingRight = getCompoundPaddingRight();
   3909                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   3910 
   3911                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
   3912                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
   3913                 }
   3914             }
   3915 
   3916             invalidate(dirty.left + scrollX, dirty.top + scrollY,
   3917                     dirty.right + scrollX, dirty.bottom + scrollY);
   3918         }
   3919     }
   3920 
   3921     @Override
   3922     protected void onDraw(Canvas canvas) {
   3923         restartMarqueeIfNeeded();
   3924 
   3925         // Draw the background for this view
   3926         super.onDraw(canvas);
   3927 
   3928         final int compoundPaddingLeft = getCompoundPaddingLeft();
   3929         final int compoundPaddingTop = getCompoundPaddingTop();
   3930         final int compoundPaddingRight = getCompoundPaddingRight();
   3931         final int compoundPaddingBottom = getCompoundPaddingBottom();
   3932         final int scrollX = mScrollX;
   3933         final int scrollY = mScrollY;
   3934         final int right = mRight;
   3935         final int left = mLeft;
   3936         final int bottom = mBottom;
   3937         final int top = mTop;
   3938 
   3939         final Drawables dr = mDrawables;
   3940         if (dr != null) {
   3941             /*
   3942              * Compound, not extended, because the icon is not clipped
   3943              * if the text height is smaller.
   3944              */
   3945 
   3946             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
   3947             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
   3948 
   3949             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3950             // Make sure to update invalidateDrawable() when changing this code.
   3951             if (dr.mDrawableLeft != null) {
   3952                 canvas.save();
   3953                 canvas.translate(scrollX + mPaddingLeft,
   3954                                  scrollY + compoundPaddingTop +
   3955                                  (vspace - dr.mDrawableHeightLeft) / 2);
   3956                 dr.mDrawableLeft.draw(canvas);
   3957                 canvas.restore();
   3958             }
   3959 
   3960             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3961             // Make sure to update invalidateDrawable() when changing this code.
   3962             if (dr.mDrawableRight != null) {
   3963                 canvas.save();
   3964                 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
   3965                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
   3966                 dr.mDrawableRight.draw(canvas);
   3967                 canvas.restore();
   3968             }
   3969 
   3970             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3971             // Make sure to update invalidateDrawable() when changing this code.
   3972             if (dr.mDrawableTop != null) {
   3973                 canvas.save();
   3974                 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
   3975                         scrollY + mPaddingTop);
   3976                 dr.mDrawableTop.draw(canvas);
   3977                 canvas.restore();
   3978             }
   3979 
   3980             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3981             // Make sure to update invalidateDrawable() when changing this code.
   3982             if (dr.mDrawableBottom != null) {
   3983                 canvas.save();
   3984                 canvas.translate(scrollX + compoundPaddingLeft +
   3985                         (hspace - dr.mDrawableWidthBottom) / 2,
   3986                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
   3987                 dr.mDrawableBottom.draw(canvas);
   3988                 canvas.restore();
   3989             }
   3990         }
   3991 
   3992         if (mPreDrawState == PREDRAW_DONE) {
   3993             final ViewTreeObserver observer = getViewTreeObserver();
   3994             if (observer != null) {
   3995                 observer.removeOnPreDrawListener(this);
   3996                 mPreDrawState = PREDRAW_NOT_REGISTERED;
   3997             }
   3998         }
   3999 
   4000         int color = mCurTextColor;
   4001 
   4002         if (mLayout == null) {
   4003             assumeLayout();
   4004         }
   4005 
   4006         Layout layout = mLayout;
   4007         int cursorcolor = color;
   4008 
   4009         if (mHint != null && mText.length() == 0) {
   4010             if (mHintTextColor != null) {
   4011                 color = mCurHintTextColor;
   4012             }
   4013 
   4014             layout = mHintLayout;
   4015         }
   4016 
   4017         mTextPaint.setColor(color);
   4018         mTextPaint.drawableState = getDrawableState();
   4019 
   4020         canvas.save();
   4021         /*  Would be faster if we didn't have to do this. Can we chop the
   4022             (displayable) text so that we don't need to do this ever?
   4023         */
   4024 
   4025         int extendedPaddingTop = getExtendedPaddingTop();
   4026         int extendedPaddingBottom = getExtendedPaddingBottom();
   4027 
   4028         float clipLeft = compoundPaddingLeft + scrollX;
   4029         float clipTop = extendedPaddingTop + scrollY;
   4030         float clipRight = right - left - compoundPaddingRight + scrollX;
   4031         float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
   4032 
   4033         if (mShadowRadius != 0) {
   4034             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
   4035             clipRight += Math.max(0, mShadowDx + mShadowRadius);
   4036 
   4037             clipTop += Math.min(0, mShadowDy - mShadowRadius);
   4038             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
   4039         }
   4040 
   4041         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
   4042 
   4043         int voffsetText = 0;
   4044         int voffsetCursor = 0;
   4045 
   4046         // translate in by our padding
   4047         {
   4048             /* shortcircuit calling getVerticaOffset() */
   4049             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4050                 voffsetText = getVerticalOffset(false);
   4051                 voffsetCursor = getVerticalOffset(true);
   4052             }
   4053             canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
   4054         }
   4055 
   4056         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   4057             if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
   4058                     (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
   4059                 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
   4060                         getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
   4061             }
   4062 
   4063             if (mMarquee != null && mMarquee.isRunning()) {
   4064                 canvas.translate(-mMarquee.mScroll, 0.0f);
   4065             }
   4066         }
   4067 
   4068         Path highlight = null;
   4069         int selStart = -1, selEnd = -1;
   4070 
   4071         //  If there is no movement method, then there can be no selection.
   4072         //  Check that first and attempt to skip everything having to do with
   4073         //  the cursor.
   4074         //  XXX This is not strictly true -- a program could set the
   4075         //  selection manually if it really wanted to.
   4076         if (mMovement != null && (isFocused() || isPressed())) {
   4077             selStart = getSelectionStart();
   4078             selEnd = getSelectionEnd();
   4079 
   4080             if (mCursorVisible && selStart >= 0 && isEnabled()) {
   4081                 if (mHighlightPath == null)
   4082                     mHighlightPath = new Path();
   4083 
   4084                 if (selStart == selEnd) {
   4085                     if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
   4086                         if (mHighlightPathBogus) {
   4087                             mHighlightPath.reset();
   4088                             mLayout.getCursorPath(selStart, mHighlightPath, mText);
   4089                             mHighlightPathBogus = false;
   4090                         }
   4091 
   4092                         // XXX should pass to skin instead of drawing directly
   4093                         mHighlightPaint.setColor(cursorcolor);
   4094                         mHighlightPaint.setStyle(Paint.Style.STROKE);
   4095 
   4096                         highlight = mHighlightPath;
   4097                     }
   4098                 } else {
   4099                     if (mHighlightPathBogus) {
   4100                         mHighlightPath.reset();
   4101                         mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
   4102                         mHighlightPathBogus = false;
   4103                     }
   4104 
   4105                     // XXX should pass to skin instead of drawing directly
   4106                     mHighlightPaint.setColor(mHighlightColor);
   4107                     mHighlightPaint.setStyle(Paint.Style.FILL);
   4108 
   4109                     highlight = mHighlightPath;
   4110                 }
   4111             }
   4112         }
   4113 
   4114         /*  Comment out until we decide what to do about animations
   4115         boolean isLinearTextOn = false;
   4116         if (currentTransformation != null) {
   4117             isLinearTextOn = mTextPaint.isLinearTextOn();
   4118             Matrix m = currentTransformation.getMatrix();
   4119             if (!m.isIdentity()) {
   4120                 // mTextPaint.setLinearTextOn(true);
   4121             }
   4122         }
   4123         */
   4124 
   4125         final InputMethodState ims = mInputMethodState;
   4126         if (ims != null && ims.mBatchEditNesting == 0) {
   4127             InputMethodManager imm = InputMethodManager.peekInstance();
   4128             if (imm != null) {
   4129                 if (imm.isActive(this)) {
   4130                     boolean reported = false;
   4131                     if (ims.mContentChanged || ims.mSelectionModeChanged) {
   4132                         // We are in extract mode and the content has changed
   4133                         // in some way... just report complete new text to the
   4134                         // input method.
   4135                         reported = reportExtractedText();
   4136                     }
   4137                     if (!reported && highlight != null) {
   4138                         int candStart = -1;
   4139                         int candEnd = -1;
   4140                         if (mText instanceof Spannable) {
   4141                             Spannable sp = (Spannable)mText;
   4142                             candStart = EditableInputConnection.getComposingSpanStart(sp);
   4143                             candEnd = EditableInputConnection.getComposingSpanEnd(sp);
   4144                         }
   4145                         imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
   4146                     }
   4147                 }
   4148 
   4149                 if (imm.isWatchingCursor(this) && highlight != null) {
   4150                     highlight.computeBounds(ims.mTmpRectF, true);
   4151                     ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
   4152 
   4153                     canvas.getMatrix().mapPoints(ims.mTmpOffset);
   4154                     ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
   4155 
   4156                     ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
   4157 
   4158                     ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
   4159                             (int)(ims.mTmpRectF.top + 0.5),
   4160                             (int)(ims.mTmpRectF.right + 0.5),
   4161                             (int)(ims.mTmpRectF.bottom + 0.5));
   4162 
   4163                     imm.updateCursor(this,
   4164                             ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
   4165                             ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
   4166                 }
   4167             }
   4168         }
   4169 
   4170         layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
   4171 
   4172         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
   4173             canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
   4174             layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
   4175         }
   4176 
   4177         /*  Comment out until we decide what to do about animations
   4178         if (currentTransformation != null) {
   4179             mTextPaint.setLinearTextOn(isLinearTextOn);
   4180         }
   4181         */
   4182 
   4183         canvas.restore();
   4184 
   4185         updateCursorControllerPositions();
   4186     }
   4187 
   4188     /**
   4189      * Update the positions of the CursorControllers.  Needed by WebTextView,
   4190      * which does not draw.
   4191      * @hide
   4192      */
   4193     protected void updateCursorControllerPositions() {
   4194         if (mInsertionPointCursorController != null &&
   4195                 mInsertionPointCursorController.isShowing()) {
   4196             mInsertionPointCursorController.updatePosition();
   4197         }
   4198 
   4199         if (mSelectionModifierCursorController != null &&
   4200                 mSelectionModifierCursorController.isShowing()) {
   4201             mSelectionModifierCursorController.updatePosition();
   4202         }
   4203     }
   4204 
   4205     @Override
   4206     public void getFocusedRect(Rect r) {
   4207         if (mLayout == null) {
   4208             super.getFocusedRect(r);
   4209             return;
   4210         }
   4211 
   4212         int sel = getSelectionEnd();
   4213         if (sel < 0) {
   4214             super.getFocusedRect(r);
   4215             return;
   4216         }
   4217 
   4218         int line = mLayout.getLineForOffset(sel);
   4219         r.top = mLayout.getLineTop(line);
   4220         r.bottom = mLayout.getLineBottom(line);
   4221 
   4222         r.left = (int) mLayout.getPrimaryHorizontal(sel);
   4223         r.right = r.left + 1;
   4224 
   4225         // Adjust for padding and gravity.
   4226         int paddingLeft = getCompoundPaddingLeft();
   4227         int paddingTop = getExtendedPaddingTop();
   4228         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4229             paddingTop += getVerticalOffset(false);
   4230         }
   4231         r.offset(paddingLeft, paddingTop);
   4232     }
   4233 
   4234     /**
   4235      * Return the number of lines of text, or 0 if the internal Layout has not
   4236      * been built.
   4237      */
   4238     public int getLineCount() {
   4239         return mLayout != null ? mLayout.getLineCount() : 0;
   4240     }
   4241 
   4242     /**
   4243      * Return the baseline for the specified line (0...getLineCount() - 1)
   4244      * If bounds is not null, return the top, left, right, bottom extents
   4245      * of the specified line in it. If the internal Layout has not been built,
   4246      * return 0 and set bounds to (0, 0, 0, 0)
   4247      * @param line which line to examine (0..getLineCount() - 1)
   4248      * @param bounds Optional. If not null, it returns the extent of the line
   4249      * @return the Y-coordinate of the baseline
   4250      */
   4251     public int getLineBounds(int line, Rect bounds) {
   4252         if (mLayout == null) {
   4253             if (bounds != null) {
   4254                 bounds.set(0, 0, 0, 0);
   4255             }
   4256             return 0;
   4257         }
   4258         else {
   4259             int baseline = mLayout.getLineBounds(line, bounds);
   4260 
   4261             int voffset = getExtendedPaddingTop();
   4262             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4263                 voffset += getVerticalOffset(true);
   4264             }
   4265             if (bounds != null) {
   4266                 bounds.offset(getCompoundPaddingLeft(), voffset);
   4267             }
   4268             return baseline + voffset;
   4269         }
   4270     }
   4271 
   4272     @Override
   4273     public int getBaseline() {
   4274         if (mLayout == null) {
   4275             return super.getBaseline();
   4276         }
   4277 
   4278         int voffset = 0;
   4279         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4280             voffset = getVerticalOffset(true);
   4281         }
   4282 
   4283         return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
   4284     }
   4285 
   4286     @Override
   4287     public boolean onKeyDown(int keyCode, KeyEvent event) {
   4288         int which = doKeyDown(keyCode, event, null);
   4289         if (which == 0) {
   4290             // Go through default dispatching.
   4291             return super.onKeyDown(keyCode, event);
   4292         }
   4293 
   4294         return true;
   4295     }
   4296 
   4297     @Override
   4298     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   4299         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
   4300 
   4301         int which = doKeyDown(keyCode, down, event);
   4302         if (which == 0) {
   4303             // Go through default dispatching.
   4304             return super.onKeyMultiple(keyCode, repeatCount, event);
   4305         }
   4306         if (which == -1) {
   4307             // Consumed the whole thing.
   4308             return true;
   4309         }
   4310 
   4311         repeatCount--;
   4312 
   4313         // We are going to dispatch the remaining events to either the input
   4314         // or movement method.  To do this, we will just send a repeated stream
   4315         // of down and up events until we have done the complete repeatCount.
   4316         // It would be nice if those interfaces had an onKeyMultiple() method,
   4317         // but adding that is a more complicated change.
   4318         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
   4319         if (which == 1) {
   4320             mInput.onKeyUp(this, (Editable)mText, keyCode, up);
   4321             while (--repeatCount > 0) {
   4322                 mInput.onKeyDown(this, (Editable)mText, keyCode, down);
   4323                 mInput.onKeyUp(this, (Editable)mText, keyCode, up);
   4324             }
   4325             if (mError != null && !mErrorWasChanged) {
   4326                 setError(null, null);
   4327             }
   4328 
   4329         } else if (which == 2) {
   4330             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
   4331             while (--repeatCount > 0) {
   4332                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
   4333                 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
   4334             }
   4335         }
   4336 
   4337         return true;
   4338     }
   4339 
   4340     /**
   4341      * Returns true if pressing ENTER in this field advances focus instead
   4342      * of inserting the character.  This is true mostly in single-line fields,
   4343      * but also in mail addresses and subjects which will display on multiple
   4344      * lines but where it doesn't make sense to insert newlines.
   4345      */
   4346     private boolean shouldAdvanceFocusOnEnter() {
   4347         if (mInput == null) {
   4348             return false;
   4349         }
   4350 
   4351         if (mSingleLine) {
   4352             return true;
   4353         }
   4354 
   4355         if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
   4356             int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
   4357 
   4358             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
   4359                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
   4360                 return true;
   4361             }
   4362         }
   4363 
   4364         return false;
   4365     }
   4366 
   4367     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
   4368         if (!isEnabled()) {
   4369             return 0;
   4370         }
   4371 
   4372         switch (keyCode) {
   4373             case KeyEvent.KEYCODE_ENTER:
   4374                 mEnterKeyIsDown = true;
   4375                 // If ALT modifier is held, then we always insert a
   4376                 // newline character.
   4377                 if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
   4378 
   4379                     // When mInputContentType is set, we know that we are
   4380                     // running in a "modern" cupcake environment, so don't need
   4381                     // to worry about the application trying to capture
   4382                     // enter key events.
   4383                     if (mInputContentType != null) {
   4384 
   4385                         // If there is an action listener, given them a
   4386                         // chance to consume the event.
   4387                         if (mInputContentType.onEditorActionListener != null &&
   4388                                 mInputContentType.onEditorActionListener.onEditorAction(
   4389                                 this, EditorInfo.IME_NULL, event)) {
   4390                             mInputContentType.enterDown = true;
   4391                             // We are consuming the enter key for them.
   4392                             return -1;
   4393                         }
   4394                     }
   4395 
   4396                     // If our editor should move focus when enter is pressed, or
   4397                     // this is a generated event from an IME action button, then
   4398                     // don't let it be inserted into the text.
   4399                     if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
   4400                             || shouldAdvanceFocusOnEnter()) {
   4401                         return -1;
   4402                     }
   4403                 }
   4404                 break;
   4405 
   4406             case<